Initial import of kotlinpoet from upstream master am: 8b6cde9a0e am: 920796b32b am: 3870e30a31

Original change: https://android-review.googlesource.com/c/platform/external/kotlinpoet/+/2413152

Change-Id: Ic663a7bfaf78e703948aa55c9b9555d87a035ebd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..3ac5661
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{kt,kts}]
+ij_kotlin_imports_layout = *
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..27fc7c2
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto eol=lf
+
+*.bat text eol=crlf
+*.jar binary
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..cbe36f6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,20 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+A sample Github project that reproduces the problem is ideal. Alternatively, please provide a failing unit test that triggers the bug.
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..11fc491
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..46c3189
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,97 @@
+name: Build
+
+on: [push, pull_request]
+
+env:
+  GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
+
+jobs:
+  jvm:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Validate Gradle Wrapper
+        uses: gradle/wrapper-validation-action@v1
+
+      - name: Configure JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 18
+
+      - name: Test
+        run: ./gradlew build
+
+  build-docs:
+    runs-on: ubuntu-latest
+    if: github.repository == 'square/kotlinpoet' && github.ref != 'refs/heads/master'
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Configure JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 18
+
+      - name: Prep docs
+        run: ./gradlew dokkaHtml
+
+      - name: Set up Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.8
+
+      - name: Build mkdocs
+        run: |
+          pip3 install -r .github/workflows/mkdocs-requirements.txt
+          mkdocs build
+
+  publish:
+    runs-on: ubuntu-latest
+    if: github.repository == 'square/kotlinpoet' && github.ref == 'refs/heads/master'
+    needs:
+      - jvm
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Configure JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 18
+
+      - name: Upload Artifacts
+        run: ./gradlew publish
+        env:
+          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
+          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
+
+      - name: Prep docs
+        run: ./gradlew dokkaHtml
+
+      - name: Set up Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.8
+
+      - name: Build mkdocs
+        run: |
+          pip3 install -r .github/workflows/mkdocs-requirements.txt
+          mkdocs build
+
+      - name: Deploy 🚀
+        if: success() && github.ref == 'refs/heads/master'
+        uses: JamesIves/github-pages-deploy-action@releases/v3
+        with:
+          GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
+          BRANCH: gh-pages # The branch the action should deploy to.
+          FOLDER: site # The folder the action should deploy.
+          SINGLE_COMMIT: true
diff --git a/.github/workflows/mkdocs-requirements.txt b/.github/workflows/mkdocs-requirements.txt
new file mode 100644
index 0000000..67c640f
--- /dev/null
+++ b/.github/workflows/mkdocs-requirements.txt
@@ -0,0 +1,19 @@
+click==8.1.3
+future==0.18.3
+Jinja2==3.1.2
+livereload==2.6.3
+lunr==0.6.2
+Markdown==3.3.7 # See https://github.com/mkdocs/mkdocs/issues/2892.
+MarkupSafe==2.1.2
+mkdocs==1.4.2
+mkdocs-macros-plugin==0.7.0
+mkdocs-material==9.0.9
+mkdocs-material-extensions==1.1.1
+Pygments==2.14.0
+pymdown-extensions==9.9.2
+python-dateutil==2.8.2
+PyYAML==6.0
+repackage==0.7.3
+six==1.16.0
+termcolor==2.2.0
+tornado==6.2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..75e7808
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+.classpath
+.gradle
+.project
+.settings
+eclipsebin
+local.properties
+
+bin
+gen
+build
+out
+lib
+reports
+
+.idea
+*.iml
+classes
+
+# Mkdocs files
+docs/1.x/*
+
+obj
+
+.DS_Store
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..ef85457
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["external_kotlinpoet_license"],
+}
+
+license {
+    name: "external_kotlinpoet_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "LICENSE.txt",
+    ],
+}
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..85de3d4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE.txt
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..62589ed
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        https://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..95ed8d4
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "kotlinpoet"
+description:
+    "A Kotlin API for generating .kt source files"
+
+third_party {
+  url {
+    type: HOMEPAGE
+    value: "https://square.github.io/kotlinpoet/"
+  }
+  url {
+    type: GIT
+    value: "https://github.com/square/kotlinpoet.git"
+  }
+  version: "c99ed244c6e4c7f8594f75566bb19ec62fa9c80d"
+  last_upgrade_date { year: 2023 month: 1 day: 31 }
+  license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2281201
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+krzysio@google.com
+danysantiago@google.com
+aurimas@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..90ee21b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+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.
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..f987b6e
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,26 @@
+Releasing
+=========
+
+ 1. Change the version in `gradle.properties` to a non-SNAPSHOT version.
+ 2. Update `docs/changelog.md` for the impending release.
+ 3. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version)
+ 4. `./gradlew clean publish --no-daemon --no-parallel`.
+ 5. Visit [Sonatype Nexus][sonatype] and ensure there's only one staging repository.
+ 6. `./gradlew closeAndReleaseRepository`.
+ 7. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version).
+ 8. Update `gradle.properties` to the next SNAPSHOT version.
+ 9. `git commit -am "Prepare next development version."`.
+ 10. `git push && git push --tags`.
+
+If steps 5-6 fail, drop the Sonatype repo, fix the problem, commit, and start again at step 4.
+
+
+Prerequisites
+-------------
+
+In `~/.gradle/gradle.properties`, set the following:
+
+ * `ORG_GRADLE_PROJECT_mavenCentralUsername` - Sonatype username for releasing to `com.squareup`.
+ * `ORG_GRADLE_PROJECT_mavenCentralPassword` - Sonatype password for releasing to `com.squareup`.
+
+ [sonatype]: https://s01.oss.sonatype.org/
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..421de38
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+import com.diffplug.gradle.spotless.SpotlessExtension
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+  alias(libs.plugins.kotlin.jvm) apply false
+  alias(libs.plugins.ksp) apply false
+  alias(libs.plugins.dokka) apply false
+  alias(libs.plugins.spotless) apply false
+  alias(libs.plugins.mavenPublish) apply false
+  alias(libs.plugins.kotlinBinaryCompatibilityValidator)
+}
+
+allprojects {
+  group = property("GROUP") as String
+  version = property("VERSION_NAME") as String
+
+  repositories {
+    mavenCentral()
+  }
+}
+
+subprojects {
+  tasks.withType<KotlinCompile> {
+    kotlinOptions {
+      freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn")
+    }
+  }
+  // Ensure "org.gradle.jvm.version" is set to "8" in Gradle metadata.
+  tasks.withType<JavaCompile> {
+    sourceCompatibility = JavaVersion.VERSION_1_8.toString()
+    targetCompatibility = JavaVersion.VERSION_1_8.toString()
+  }
+
+  apply(plugin = "org.jetbrains.kotlin.jvm")
+  if ("test" !in name && buildFile.exists()) {
+    apply(plugin = "org.jetbrains.dokka")
+    apply(plugin = "com.vanniktech.maven.publish")
+    configure<KotlinProjectExtension> {
+      explicitApi()
+    }
+    afterEvaluate {
+      tasks.named<DokkaTask>("dokkaHtml") {
+        val projectFolder = project.path.trim(':').replace(':', '-')
+        outputDirectory.set(rootProject.rootDir.resolve("docs/1.x/$projectFolder"))
+        dokkaSourceSets.configureEach {
+          skipDeprecated.set(true)
+        }
+      }
+    }
+  }
+
+  apply(plugin = "com.diffplug.spotless")
+  configure<SpotlessExtension> {
+    kotlin {
+      target("**/*.kt")
+      ktlint(libs.versions.ktlint.get()).editorConfigOverride(
+        mapOf("ktlint_standard_filename" to "disabled"),
+      )
+      trimTrailingWhitespace()
+      endWithNewline()
+
+      licenseHeader(
+        """
+        |/*
+        | * Copyright (C) ${'$'}YEAR 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.
+        | */
+        """.trimMargin()
+      )
+    }
+  }
+
+  // Copied from https://github.com/square/retrofit/blob/master/retrofit/build.gradle#L28.
+  // Create a test task for each supported JDK.
+  for (majorVersion in 8..18) {
+    // Adoptium JDK 9 cannot extract on Linux or Mac OS.
+    if (majorVersion == 9) continue
+    // Started causing build failures in late 2022, e.g.:
+    // https://github.com/square/kotlinpoet/actions/runs/3816320722/jobs/6531532305.
+    if (majorVersion == 10) continue
+
+    val jdkTest = tasks.register<Test>("testJdk$majorVersion") {
+      val javaToolchains = project.extensions.getByType(JavaToolchainService::class)
+      javaLauncher.set(javaToolchains.launcherFor {
+        languageVersion.set(JavaLanguageVersion.of(majorVersion))
+      })
+
+      description = "Runs the test suite on JDK $majorVersion"
+      group = LifecycleBasePlugin.VERIFICATION_GROUP
+
+      // Copy inputs from normal Test task.
+      val testTask = tasks.getByName<Test>("test")
+      classpath = testTask.classpath
+      testClassesDirs = testTask.testClassesDirs
+    }
+    tasks.named("check").configure {
+      dependsOn(jdkTest)
+    }
+  }
+}
+
+apiValidation {
+  nonPublicMarkers += "com.squareup.kotlinpoet.ExperimentalKotlinPoetApi"
+  ignoredProjects += listOf(
+    "interop", // Empty middle package
+    "test-processor" // Test only
+  )
+}
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 0000000..05680e6
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,588 @@
+Change Log
+==========
+
+## Version 1.12.0
+
+_2022-06-13_
+
+Thanks to [@WhosNickDoglio][WhosNickDoglio], [@sullis][sullis], [@DRSchlaubi][DRSchlaubi],
+[@martinbonnin][martinbonnin], [@seriouslyhypersonic][seriouslyhypersonic], [@ephemient][ephemient],
+[@dkilmer][dkilmer], [@aksh1618][aksh1618], [@zsqw123][zsqw123], [@roihershberg][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-ksp` API promoted to stable.
+ * Fix: Resolve enum constants when emitting types.
+ * Fix: Fix type argument mapping when processing typealiases with KSP.
+ * Fix: Properly unwrap `KSTypeAlias` with an unused type parameter.
+ * Fix: Unwrap nested `KSTypeAlias`-es recursively.
+ * Fix: Add support for context receivers `@PropertySpec` and fix issues with annotations.
+ * Fix: Treat `header` and `impl` as keywords (workaround for KT-52315).
+ * Fix: Use `%N` instead of `%L` for annotation arg names so keywords are handled.
+ * Fix: Improve handling of long `return` expressions.
+
+## Version 1.11.0
+
+_2022-03-24_
+
+Thanks to [@liujingxing][liujingxing] and [@BoD][BoD] for contributing to this release.
+
+* New: Kotlin scripting support in `FileSpec`.
+
+```kotlin
+val spec = FileSpec.scriptBuilder("Taco")
+  .addStatement("println(%S)", "hello world!")
+  .addKotlinDefaultImports()
+  .build()
+```
+
+Generates a `Taco.kts` file with the following contents:
+
+```kotlin
+println("hello world!")
+```
+
+* New: Emit trailing commas for multi-line parameters and annotations.
+* New: Add `KSAnnotation.toAnnotationSpec()`.
+* New: Add `Unit` and `CharSequence` conversions in `javapoet-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 `val` property if its getter is `inline`.
+* Fix: Add `yield` to the list of reserved keywords.
+* Fix: Enforce only allowed parameter modifiers in `ParameterSpec` (i.e. `crossinline`, `vararg`, and `noinline`).
+* Fix: Fix `CodeBlock`s in class delegation getting `toString()`'d instead of participating in code writing.
+* Fix: Error when attempting to convert KSP error types (i.e. if `KSType.isError` is true) to `TypeName`.
+
+## Version 1.10.2
+
+_2021-10-22_
+
+Thanks to [@glureau][glureau] and [@goooler][goooler] for contributing to this release.
+
+* New: Switch `AnnotationSpec.get()` to use the `arrayOf()` 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 `@Deprecated` annotations on synthetic methods for annotations.
+
+## Version 1.10.1
+
+_2021-09-21_
+
+Thanks to [@evant][evant] for contributing to this release.
+
+ * Fix: Correct generation of typealiases with type args in KSP interop.
+ * Fix: Add missing default `TypeParameterResolver.EMPTY` argument to
+   `fun KSTypeArgument.toTypeName` in KSP interop.
+
+## Version 1.10.0
+
+_2021-09-20_
+
+Thanks to [@martinbonnin][martinbonnin], [@idanakav][idanakav], [@goooler][goooler], and
+[@anandwana001][anandwana001] for contributing to this release.
+
+ * New: Add a new [KSP][ksp] interop artifact. See [docs][ksp-interop-docs] for more details.
+ * New: Add a new [JavaPoet][javapoet] interop artifact. See [docs][javapoet-interop-docs] for more
+   details.
+ * New: Allow copying a `ParameterizedTypeName` with new type arguments via new `copy()` overload.
+ * kotlinx-metadata artifacts have been consolidated to a single `com.squareup:kotlinpoet-metadata`
+   maven artifact. The previous `kotlinpoet-metadata-*` subartifacts are no longer published.
+ * New: `TypeNameAliasTag` has been moved to KotlinPoet's main artifact under `TypeAliasTag`, for
+   reuse with KSP interop.
+ * `ImmutableKm*` classes have been removed. They were deemed to be a needless abstraction over the base `kotlinx-metadata` Km 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-reflect` usage.
+ * Avoid using kotlin-reflect for looking up `Unit` types 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 `@DelicateKotlinPoetApi` annotation.
+ * New: `CodeBlock.Builder.withIndent` helper function.
+ * New: Allow changing initializers and default values in `ParameterSpec.Builder` and
+   `PropertySpec.Builder` after they were set.
+ * New: `MemberName.isExtension` property 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.VALUE` to support `value class` declarations.
+ * New: Allow using a custom `ClassLoader` with `ReflectiveClassInspector`.
+ * New: Update to kotlinx-metadata 0.2.0.
+ * Fix: Ensure `ImmutableKmProperty.toMutable()` copies `fieldSignature`.
+ * Fix: Prevent name clashes between an imported `MemberName` and 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 `expect` and `external` functions.
+ * Fix: Don't allow `expect` or `external` classes to initialize supertypes.
+ * Fix: Disallow delegate constructor calls in `external` classes.
+ * Fix: Allow non-public primary constructors inside inline/value classes.
+ * Fix: Allow init blocks inside inline/value classes.
+ * Fix: Omit redundant `abstract` modifiers on members inside interfaces
+
+## Version 1.7.2
+
+_2020-10-20_
+
+ * New: Detect expression bodies with `return·` and `throw·` 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][issue-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][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()` and
+   `FunSpec.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 as
+     `java.lang.String`.
+   - Type nullability information is not accessible.
+   - `suspend` functions are seen as simple functions with an additional `Continuation` parameter.
+
+   The correct solution is to switch to [KotlinPoet-metadata][kotlinpoet-metadata] or
+   [KotlinPoet-metadata-specs][kotlinpoet-metadata-specs] API, which fetches Kotlin-specific
+   information from the `@Metadata` annotation and produces correct KotlinPoet Specs. We may explore
+   adding new metadata-based alternatives to the deprecated APIs in the future.
+
+ * New: Kotlin 1.3.72.
+ * New: Improve `MemberName` to support operator overloading.
+ * New: Support generics in `AnnotationSpec`.
+ * New: Add support for functional interfaces.
+ * New: Make more `FunSpec.Builder` members 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 `LambdaTypeName` annotations 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 `AnnotationSpec`s with the annotation mirror when available.
+ * New: Include annotations on enum entries when creating `TypeSpec`s 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 `TypeSpec`s 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 the `TypeSpec` body.
+ * 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 Kotlin `Metadata`.
+
+## 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 the
+   `toTypeSpec()`/`toFileSpec()` APIs in `kotlinpoet-metadata-specs` artifact to inform about
+   Classes with information that isn’t present in metadata (overrides, JVM modifiers, etc). There
+   are two batteries-included implementations available in `ReflectiveClassInspector`
+   (for reflection) and `ElementsClassInspector` (for the javax Elements API in annotation
+   processing). These implementations are available through their respective
+   `kotlinpoet-classinspector-*` artifacts. For more information refer to the
+   [KotlinPoet-metadata-specs README][kotlinpoet-metadata-specs].
+
+   At the time of this release the API is in experimental mode and has to be opted into via the
+   `KotlinPoetMetadataPreview` annotation.
+
+ * New: Kotlin 1.3.50.
+ * New: A new constructor to simplify creation of `ParameterSpec` instances.
+ * New: New `ClassName` constructors.
+ * New: `TypeName` and subclasses can now store tags.
+ * New: Optional parameters added to `toBuilder()` methods of most Specs.
+ * New: `List` overrides for Spec methods that accept `vararg`s.
+ * 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: `TypeVariableName`s with empty bounds will now default to `Any?`.
+ * Fix: Don't emit parens for primary constructors.
+ * Fix: `ClassName`s 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 `%M` and `MemberName` type.
+ * New: Add extensions for getting a `MemberName` from a `ClassName`, `KClass` and `Class`.
+ * New: Allow passing `CodeBlock`s 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 `LineWrapper` to enable proper line wrapping in KDoc.
+ * Fix: Add newline for `TypeSpec` Kdoc 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 a `TypeSpec`.
+ * 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 `%P` placeholder for string templates.
+ * New: Add support for receiver kdoc.
+ * New: Avoid emitting `Unit` as 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 `%W` with space, and add `·` as a non-breaking space.
+ * New: Change `TypeName` to sealed class.
+ * New: Documentation improvements.
+ * New: Replace `TypeName` modifier methods with `copy()`.
+ * New: Rename members of `WildcardTypeName` to match with the producer/consumer generics model.
+ * New: Rename `TypeName.nullable` into `TypeName.isNullable`.
+ * New: Rename `LambdaTypeName.suspending` into `LambdaTypeName.isSuspending`.
+ * New: Rename `TypeVariableName.reified` into `TypeVariableName.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 `TypeVariable`s in `PropertySpec`.
+ * 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 `CodeBlock`s with statements as property initializers and default parameter values.
+ * New: Rename `NameAllocator.clone()` into `NameAllocator.copy().
+ * New: Rename `TypeName.asNonNullable()` to `TypeName.asNonNull()`.
+ * New: Remove `PropertySpec.varBuilder()` (use `mutable()` instead).
+ * New: Allow importing top-level members in default package.
+ * New: Add overloads to add KDoc to return type.
+ * Fix: Distinguishing `IntArray` and `Array<Int>` when creating `TypeName`.
+ * Fix: Use `TypeName` instead of `ClassName` as parameter type of `plusParameter()`.
+ * Fix: Keep type-parameter variance when constructing `TypeName` from `KType`.
+ * Fix: Don't validate modifiers when merging properties with primary constructor parameters.
+ * Fix: Escape $ characters in formatted strings.
+ * Fix: `FileSpec.Builder` blank 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 `external` support.
+ * New: Extensions for `KType` and `KTypeParameter`.
+ * 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 `ParameterizedTypeName` API.
+ * New: Add `WildcardTypeName.STAR` constant.
+ * 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 `TypeVariable` bound 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 `PropertySpec`s as `CodeBlock` literals.
+ * New: Support KDoc for `TypeAliasSpec`.
+ * New: Allow for adding an initializer block inside a companion object.
+ * New: Escape name in `ParameterSpec` which 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 `LambdaTypeName` parameters.
+ * 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()` to `addFunction()`.
+ * New: Rename `KotlinFile` to `FileSpec`.
+ * New: Rename `KotlinFile.addFileAnnotation()` to `addAnnotation()`.
+ * New: Rename `KotlinFile.addFileComment()` to `addComment()`.
+ * New: Support cross-platform code, including `HEADER` and `IMPL` modifiers.
+ * 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 `constructor` keyword.
+ * 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 `AnnotationSpec` for throws.
+
+
+## Version 0.3.0
+
+_2017-06-11_
+
+ * New: Objects and companion objects.
+ * New: `TypeAliasSpec` to create type aliases.
+ * New: `LambdaTypeName` to 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 `Filer` writing and originating elements concept. These stem from `javac` annotation
+   processors.
+ * Fix: Generate valid annotation classes.
+ * Fix: Use `KModifier` for varargs.
+ * Fix: Use `ParameterizedTypeName` for array types.
+ * Fix: Extract Kotlin name from `KClass` instead of Java name.
+ * Fix: Emit valid class literals: `Double::class` instead of `Double.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 of `extends` and `implements`.
+ * Fix: Make initializerBlock emit `init {}`.
+
+
+## Version 0.1.0
+
+_2017-05-16_
+
+ * Initial public release.
+
+ [kotlinpoet-metadata]: ../kotlinpoet_metadata
+ [kotlinpoet-metadata-specs]: ../kotlinpoet_metadata_specs
+ [explicit-api-mode]: https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors
+ [issue-999]: https://github.com/square/kotlinpoet/issues/999
+ [ksp]: https://github.com/google/ksp
+ [ksp-interop-docs]: https://square.github.io/kotlinpoet/interop-ksp/
+ [javapoet]: https://github.com/square/javapoet
+ [javapoet-interop-docs]: https://square.github.io/kotlinpoet/interop-javapoet/
+
+ [martinbonnin]: https://github.com/martinbonnin
+ [idanakav]: https://github.com/idanakav
+ [goooler]: https://github.com/goooler
+ [anandwana001]: https://github.com/anandwana001
+ [evant]: https://github.com/evant
+ [glureau]: https://github.com/glureau
+ [liujingxing]: https://github.com/liujingxing
+ [BoD]: https://github.com/BoD
+ [WhosNickDoglio]: https://github.com/WhosNickDoglio
+ [sullis]: https://github.com/sullis
+ [DRSchlaubi]: https://github.com/DRSchlaubi
+ [seriouslyhypersonic]: https://github.com/seriouslyhypersonic
+ [ephemient]: https://github.com/ephemient
+ [dkilmer]: https://github.com/dkilmer
+ [aksh1618]: https://github.com/aksh1618
+ [zsqw123]: https://github.com/zsqw123
+ [roihershberg]: https://github.com/roihershberg
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..74108a8
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,15 @@
+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`.
+
+Before your code can be accepted into the project you must also sign the
+[Individual Contributor License Agreement (CLA)][1].
+
+
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
diff --git a/docs/css/app.css b/docs/css/app.css
new file mode 100644
index 0000000..a982ae6
--- /dev/null
+++ b/docs/css/app.css
@@ -0,0 +1,30 @@
+@font-face {
+    font-family: cash-market;
+    src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Regular.woff2") format("woff2");
+    font-weight: 400;
+    font-style: normal
+}
+
+@font-face {
+    font-family: cash-market;
+    src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Medium.woff2") format("woff2");
+    font-weight: 500;
+    font-style: normal
+}
+
+@font-face {
+    font-family: cash-market;
+    src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Bold.woff2") format("woff2");
+    font-weight: 700;
+    font-style: normal
+}
+
+body, input {
+    font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
+}
+
+.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4 {
+    font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
+    line-height: normal;
+    font-weight: bold;
+}
diff --git a/docs/images/icon-square.png b/docs/images/icon-square.png
new file mode 100644
index 0000000..bdc98d1
--- /dev/null
+++ b/docs/images/icon-square.png
Binary files differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..4b8ec1a
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,1541 @@
+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:
+
+```kotlin
+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:
+
+```kotlin
+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][kdoc] catalogs the complete KotlinPoet API, which is inspired by [JavaPoet][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](https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors).
+Examples in this file omit those modifiers for brevity.
+
+### 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:
+
+```kotlin
+val main = FunSpec.builder("main")
+  .addCode("""
+    |var total = 0
+    |for (i in 0 until 10) {
+    |    total += i
+    |}
+    |""".trimMargin())
+  .build()
+```
+
+Which generates this:
+
+```kotlin
+fun main() {
+  var total = 0
+  for (i in 0 until 10) {
+    total += i
+  }
+}
+```
+
+There are additional APIs to assist with newlines, braces and indentation:
+
+```kotlin
+val main = FunSpec.builder("main")
+  .addStatement("var total = 0")
+  .beginControlFlow("for (i in 0 until 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:
+
+```kotlin
+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 until $to)")
+    .addStatement("result = result $op i")
+    .endControlFlow()
+    .addStatement("return result")
+    .build()
+}
+```
+
+And here's what we get when we call `computeRange("multiply10to20", 10, 20, "*")`:
+
+```kotlin
+fun multiply10to20(): kotlin.Int {
+  var result = 1
+  for (i in 10 until 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.
+
+### %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:
+
+```kotlin
+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:
+
+```kotlin
+class HelloWorld {
+  fun slimShady(): String = "slimShady"
+
+  fun eminem(): String = "eminem"
+
+  fun marshallMathers(): String = "marshallMathers"
+}
+```
+
+### %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:
+
+```kotlin
+val stringWithADollar = "Your total is " + "$" + "50"
+val funSpec = FunSpec.builder("printTotal")
+  .returns(String::class)
+  .addStatement("return %S", stringWithADollar)
+  .build()
+```
+
+produces:
+
+```kotlin
+fun printTotal(): String = "Your total is ${'$'}50"
+```
+
+If you need to generate string templates, use `%P`, which doesn't escape dollars:
+
+```kotlin
+val amount = 50
+val stringWithADollar = "Your total is " + "$" + "amount"
+val funSpec = FunSpec.builder("printTotal")
+  .returns(String::class)
+  .addStatement("return %P", stringWithADollar)
+  .build()
+```
+
+produces:
+
+```kotlin
+fun printTotal(): String = "Your total is $amount"
+```
+
+You can also use `CodeBlock`s as arguments to `%P`, which is handy when you need to reference
+importable types or members inside the string template:
+
+```kotlin
+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:
+
+```kotlin
+package com.example
+
+import kotlin.IntArray
+import kotlin.collections.contentToString
+
+fun print(digits: IntArray) {
+  println("""These are the digits: ${digits.contentToString()}""")
+}
+```
+
+### %T for Types
+
+KotlinPoet has rich built-in support for types, including automatic generation of `import`
+statements. Just use **`%T`** to reference **types**:
+
+```kotlin
+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`:
+
+```kotlin
+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):
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+
+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()
+```
+
+KotlinPoet will decompose each type and import its components where possible.
+
+```kotlin
+package com.example.helloworld
+
+import com.mattel.Hoverboard
+import com.misc.Thing
+import kotlin.Array
+import kotlin.collections.ArrayList
+import kotlin.collections.List
+
+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)
+  }
+}
+```
+
+#### Nullable Types
+
+KotlinPoet supports nullable types. To convert a `TypeName` into its nullable counterpart, use the
+`copy()` method with `nullable` parameter set to `true`:
+
+```kotlin
+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:
+
+```kotlin
+class HelloWorld {
+  private var java: String? = null
+
+  private val kotlin: String
+}
+```
+
+### %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:
+
+```kotlin
+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:
+
+```kotlin
+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 clashing
+`MemberName`:
+
+```kotlin
+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`:
+
+```kotlin
+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.
+
+```kotlin
+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.
+
+```kotlin
+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
+
+Generated code is often self-referential. Use **`%N`** to refer to another generated declaration by
+its name. Here's a method that calls another:
+
+```kotlin
+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`:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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)
+}
+```
+
+### %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-with
+[`String.format()`][formatter]. It accepts **`%L`** to emit a **literal** value in the output. This
+works just like `Formatter`'s `%s`:
+
+```kotlin
+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 until %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.
+
+### 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"
+
+```kotlin
+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.
+
+```kotlin
+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 in
+`a-z`, `A-Z`, `0-9`, and `_`, and must start with a lowercase character.
+
+```kotlin
+val map = LinkedHashMap<String, Any>()
+map += "food" to "tacos"
+map += "count" to 3
+CodeBlock.builder().addNamed("I ate %count:L %food:L", map)
+```
+
+### 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.
+
+```kotlin
+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:
+
+```kotlin
+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`.
+
+```kotlin
+val square = FunSpec.builder("square")
+  .receiver(Int::class)
+  .returns(Int::class)
+  .addStatement("var s = this * this")
+  .addStatement("return s")
+  .build()
+```
+
+Which outputs:
+
+```kotlin
+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:
+
+```kotlin
+val abs = FunSpec.builder("abs")
+  .addParameter("x", Int::class)
+  .returns(Int::class)
+  .addStatement("return if (x < 0) -x else x")
+  .build()
+```
+
+Which outputs:
+
+```kotlin
+fun abs(x: Int): Int = if (x < 0) -x else x
+```
+
+#### Default function arguments
+
+Consider the example below.
+Function argument `b` has a default value of 0 to avoid overloading this function.
+
+```kotlin
+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.
+
+```kotlin
+FunSpec.builder("add")
+  .addParameter("a", Int::class)
+  .addParameter(
+    ParameterSpec.builder("b", Int::class)
+      .defaultValue("%L", 0)
+      .build()
+  )
+  .addStatement("print(\"a + b = ${a + b}\")")
+  .build()
+```
+
+#### Spaces wrap by default!
+
+In order to provide meaningful formatting, KotlinPoet would replace spaces, found in blocks of code,
+with new line symbols, in cases when the line of code exceeds the length limit. Let's take this
+function for example:
+
+```kotlin
+val funSpec = FunSpec.builder("foo")
+  .addStatement("return (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }")
+  .build()
+```
+
+Depending on where it's found in the file, it may end up being printed out like this:
+
+```kotlin
+fun foo() = (100..10000).map { number -> number * number }.map { number -> number.toString() }.also
+{ string -> println(string) }
+```
+
+Unfortunately this code is broken: the compiler expects `also` and `{` to be on the same line.
+KotlinPoet is unable to understand the context of the expression and fix the formatting for you, but
+there's a trick you can use to declare a non-breaking space - use the `·` symbol where you would
+otherwise use a space. Let's apply this to our example:
+
+```kotlin
+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:
+
+```kotlin
+fun foo() = (100..10000).map { number -> number * number }.map { number ->
+  number.toString()
+}.also { string -> println(string) }
+```
+
+The code is now correct and will compile properly. It still doesn't look perfect - you can play with
+replacing other spaces in the code block with `·` symbols to achieve better formatting.
+
+### Constructors
+
+`FunSpec` is a slight misnomer; it can also be used for constructors:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+  .primaryConstructor(flux)
+  .addProperty("greeting", String::class, KModifier.PRIVATE)
+  .build()
+```
+
+This code, however, generates the following:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+class HelloWorld(private val greeting: String)
+```
+
+Notice that KotlinPoet omits `{}` for classes with empty bodies.
+
+### Parameters
+
+Declare parameters on methods and constructors with either `ParameterSpec.builder()` or
+`FunSpec`'s convenient `addParameter()` API:
+
+```kotlin
+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:
+
+```kotlin
+fun welcomeOverlords(android: String = "pie", robot: String) {
+}
+```
+
+The extended `Builder` form is necessary when the parameter has annotations (such as `@Inject`).
+
+### Properties
+
+Like parameters, properties can be created either with builders or by using convenient helper
+methods:
+
+```kotlin
+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:
+
+```kotlin
+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()`][formatter]-like syntax as the code
+blocks above:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+  .addModifiers(KModifier.PRIVATE)
+  .initializer("%S + %L", "Oreo v.", 8.1)
+  .build()
+```
+
+Which generates:
+
+```kotlin
+private val android: String = "Oreo v." + 8.1
+```
+
+By default `PropertySpec.Builder` produces `val` properties. Use `mutable()` if you need a
+`var`:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+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.
+
+### Interfaces
+
+KotlinPoet has no trouble with interfaces. Note that interface methods must always be `ABSTRACT`.
+The modifier is necessary when defining the interface:
+
+```kotlin
+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!
+
+```kotlin
+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()`.
+
+```kotlin
+val helloWorld = TypeSpec.funInterfaceBuilder("HelloWorld")
+  .addFunction(
+    FunSpec.builder("beep")
+      .addModifiers(KModifier.ABSTRACT)
+      .build()
+  )
+  .build()
+
+// Generates...
+fun interface HelloWorld {
+  fun beep()
+}
+```
+
+### Objects
+
+KotlinPoet supports objects:
+
+```kotlin
+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()`:
+
+```kotlin
+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.
+
+### Enums
+
+Use `enumBuilder` to create the enum type, and `addEnumConstant()` for each value:
+
+```kotlin
+val helloWorld = TypeSpec.enumBuilder("Roshambo")
+  .addEnumConstant("ROCK")
+  .addEnumConstant("SCISSORS")
+  .addEnumConstant("PAPER")
+  .build()
+```
+
+To generate this:
+
+```kotlin
+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:
+
+```kotlin
+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:
+
+```kotlin
+enum class Roshambo(private val handsign: String) {
+  ROCK("fist") {
+    override fun toString(): String = "avalanche!"
+  },
+
+  SCISSORS("peace"),
+
+  PAPER("flat");
+}
+```
+
+### 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`:
+
+```kotlin
+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:
+
+```kotlin
+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.
+
+### Annotations
+
+Simple annotations are easy:
+
+```kotlin
+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:
+
+```kotlin
+@Test
+fun `test string equality`() {
+  assertThat("foo").isEqualTo("foo")
+}
+```
+
+Use `AnnotationSpec.builder()` to set properties on annotations:
+
+```kotlin
+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:
+
+```kotlin
+@Headers(
+  accept = "application/json; charset=utf-8",
+  userAgent = "Square Cash"
+)
+abstract fun recordEvent(logRecord: LogRecord): LogReceipt
+```
+
+When you get fancy, annotation values can be annotations themselves. Use `%L` for embedded
+annotations:
+
+```kotlin
+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:
+
+```kotlin
+@HeaderList(
+  [
+    Header(name = "Accept", value = "application/json; charset=utf-8"),
+    Header(name = "User-Agent", value = "Square Cash")
+  ]
+)
+abstract fun recordEvent(logRecord: LogRecord): LogReceipt
+```
+
+KotlinPoet supports use-site targets for annotations:
+
+```kotlin
+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:
+
+```kotlin
+@file:JvmName
+
+package com.example
+
+import kotlin.Int
+import kotlin.jvm.JvmName
+
+fun Int.abs(): Int = if (this < 0) -this else this
+```
+
+### Type Aliases
+
+KotlinPoet provides API for creating Type Aliases, which supports simple class names, parameterized
+types and lambdas:
+
+```kotlin
+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:
+
+```kotlin
+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) -> Boolean
+```
+
+### Callable References
+
+[Callable references](https://kotlinlang.org/docs/reference/reflection.html#callable-references) to
+constructors, functions, and properties may be emitted via:
+
+- `ClassName.constructorReference()` for constructors
+- `MemberName.reference()` for functions and properties
+
+For example,
+
+```kotlin
+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:
+
+```kotlin
+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](#m-for-members).
+
+kotlin-reflect
+--------
+
+To generate source code from
+any [`KType`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-type/), including
+information that's not accessible to the builtin reflection APIs, KotlinPoet depends
+on [kotlin-reflect](https://kotlinlang.org/docs/reflection.html#jvm-dependency). `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](https://kotlinlang.org/docs/generics.html#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:
+
+```kotlin
+dependencies {
+  implementation("com.squareup:kotlinpoet:<version>") {
+    exclude(module = "kotlin-reflect")
+  }
+}
+```
+
+The main APIs that require `kotlin-reflect`
+are [`KType.asTypeName()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/as-type-name.html)
+and [`typeNameOf<T>()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/type-name-of.html).
+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:
+
+```kotlin
+// 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))
+```
+
+Download
+--------
+
+![Maven Central][version-shield]
+
+Download [the latest .jar][dl] or depend via Maven:
+
+```xml
+<dependency>
+  <groupId>com.squareup</groupId>
+  <artifactId>kotlinpoet</artifactId>
+  <version>[version]</version>
+</dependency>
+```
+
+or Gradle:
+
+```groovy
+implementation("com.squareup:kotlinpoet:[version]")
+```
+
+Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
+
+
+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.
+
+
+ [dl]: https://search.maven.org/remote_content?g=com.squareup&a=kotlinpoet&v=LATEST
+ [version-shield]: https://img.shields.io/maven-central/v/com.squareup/kotlinpoet
+ [snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/com/squareup/kotlinpoet/
+ [kdoc]: https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/
+ [javapoet]: https://github.com/square/javapoet/
+ [formatter]: https://developer.android.com/reference/java/util/Formatter.html
diff --git a/docs/interop-javapoet.md b/docs/interop-javapoet.md
new file mode 100644
index 0000000..6b41f0d
--- /dev/null
+++ b/docs/interop-javapoet.md
@@ -0,0 +1,54 @@
+JavaPoet Extensions for KotlinPoet
+==================================
+
+`interop:javapoet` is an interop API for converting [JavaPoet](https://github.com/squareup/javapoet)
+types to KotlinPoet types. This is particularly useful for projects that support code gen in
+multiple languages and want to easily be able to jump between.
+
+Note that this API is currently in preview and subject to API changes. Usage of them requires opting
+in to the `@KotlinPoetJavaPoetPreview` annotation.
+
+### Examples
+
+**Typealiases for common conflicting type names**
+
+```kotlin
+// Points to com.squareup.kotlinpoet.TypeName
+KTypeName
+// Points to com.squareup.javapoet.TypeName
+JTypeName
+```
+
+**Convert between a `JTypeName` and `KTypeName`**
+
+Most usages of these can run through the `toKTypeName()` and `toJTypeName()` extensions.
+
+```kotlin
+val jType = JTypeName.get("com.example", "Taco")
+
+// Returns a KotlinPoet `ClassName` of value `com.example.Taco`
+val kType = jType.toKTypeName()
+
+// Returns a JavaPoet `ClassName` of value `com.example.Taco`
+val jType2 = kType.toJTypeName()
+```
+
+### Intrinsics
+
+Kotlin supports a number of intrinsic types that live in the `kotlin` package, such as primitives,
+`List`, `String`, `IntArray`, etc. Where possible, interop will best-effort attempt to convert to
+the idiomatic Kotlin type when converting from the Java type.
+
+### Lossy Conversions
+
+Kotlin has more expressive types in some regards. These cannot be simply expressed in JavaPoet and
+are subject to lossy conversions.
+
+Examples include:
+
+- Nullability
+  - Nullable types in Kotlin will appear as normal types in JavaPoet.
+- Collection mutability
+  - Immutable Kotlin collections will convert to their standard (mutable) Java analogs.
+  - Java collections will convert to _immutable_ Kotlin analogs, erring on the side of safety in generated public APIs
+- Unsigned types
diff --git a/docs/interop-kotlinx-metadata.md b/docs/interop-kotlinx-metadata.md
new file mode 100644
index 0000000..58a470a
--- /dev/null
+++ b/docs/interop-kotlinx-metadata.md
@@ -0,0 +1,84 @@
+KotlinPoet-metadata
+===================
+
+`interop:kotlinx-metadata` is an API for working with Kotlin `@Metadata` annotations. Its API
+sits atop [kotlinx-metadata](https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm),
+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
+
+```kotlin
+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 kotlinx-metadata `Flags` property.
+
+Using the Taco example above, we can glean certain information:
+
+```kotlin
+println("Is class? ${kmClass.isClass}")
+println("Is data class? ${kmClass.isData}")
+```
+
+### Interop with KotlinPoet
+
+`interop:kotlinx-metadata` offers an API for converting core kotlinx-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
+
+```kotlin
+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 javax
+`Elements` implementations are available under the
+`com.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.Class` and `KotlinClassMetadata.FileFacade` are supported for now. No support for `SyntheticClass`, `MultiFileClassFacade`, or `MultiFileClassPart`
+- `@JvmOverloads` annotations are only supported with `ElementsClassInspector` and not reflection.
+- Non-const literal values are only supported with `ElementsClassInspector` and not reflection.
+- ClassInspector data sourced from `synthetic` constructs are only supported with
+  `ReflectiveClassInspector` and 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
+  or `property:` site target annotations.
+- Annotations annotated with `AnnotationRetention.SOURCE` are not parsable in reflection nor javax elements.
diff --git a/docs/interop-ksp.md b/docs/interop-ksp.md
new file mode 100644
index 0000000..78c752f
--- /dev/null
+++ b/docs/interop-ksp.md
@@ -0,0 +1,136 @@
+KSP Extensions for KotlinPoet
+==============
+
+`interop:ksp` is an interop API for converting
+[Kotlin Symbol Processing][ksp] (KSP) types to KotlinPoet types and
+writing to KSP `CodeGenerator`.
+
+```kotlin
+dependencies {
+  implementation("com.squareup:kotlinpoet-ksp:<version>")
+}
+```
+
+### Examples
+
+Examples are based on reading the following property as a `KSProperty`:
+
+```kotlin
+class Taco {
+  internal inline val seasoning: String get() = "spicy"
+}
+```
+
+**Convert a `KSType` to a `TypeName`**
+
+```kotlin
+// returns a `ClassName` of value `kotlin.String`
+seasoningKsProperty.type.toTypeName()
+```
+
+**Convert a `Modifier` to a `KModifier`**
+
+```kotlin
+// returns `[KModifier.INLINE]`
+seasoningKsProperty.modifiers.mapNotNull { it.toKModifier() }
+```
+
+**Convert a `Visibility` to a `KModifier`**
+
+```kotlin
+// 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.
+
+```kotlin
+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 a
+`List<KSTypeParameter>`.
+
+Consider the following class and function
+
+```kotlin
+abstract class Taco<T> {
+  abstract val seasoning: T
+}
+```
+
+To properly resolve the type of `seasoning`, we need to pass the class `TypeParameterResolver` to
+`toTypeName()` so that it can properly resolve it.
+
+```kotlin
+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.
+
+```kotlin
+class Taco<T> {
+  fun <E> getShellOfType(param1: E, param2: T) {
+
+  }
+}
+```
+
+To resolve its parameters, we need to create a `TypeParameterResolver` from the function's
+`typeParameters` and _compose_ it with the enclosing class's type parameters as a `parent`.
+
+```kotlin
+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][incremental] 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.
+
+```kotlin
+val functionBuilder = FunSpec.builder("sayHello")
+  .addOriginatingKSFile(sourceKsFile)
+  .build()
+```
+
+Like KotlinPoet's _originating elements_ support for javac annotation processors, calling the
+`FileSpec.writeTo(CodeGenerator, ...)` function will automatically collect and de-dupe these originating
+`KSFile` 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.
+
+ [ksp]: https://github.com/google/ksp
+ [incremental]: https://github.com/google/ksp/blob/main/docs/incremental.md
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..f0543f0
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+org.gradle.jvmargs='-Dfile.encoding=UTF-8'
+
+GROUP=com.squareup
+VERSION_NAME=1.13.0-SNAPSHOT
+
+POM_URL=https://github.com/square/kotlinpoet
+POM_SCM_URL=https://github.com/square/kotlinpoet
+POM_SCM_CONNECTION=scm:git:https://github.com/square/kotlinpoet.git
+POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/square/kotlinpoet.git
+
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+
+POM_DEVELOPER_ID=square
+POM_DEVELOPER_NAME=Square, Inc.
+
+SONATYPE_HOST=S01
+RELEASE_SIGNING_ENABLED=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..523b9bc
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,51 @@
+# Copyright (C) 2021 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.
+
+[versions]
+kotlin = "1.7.22"
+kct = "1.4.9"
+ksp = "1.7.22-1.0.8"
+ktlint = "0.48.2"
+
+[plugins]
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+dokka = { id = "org.jetbrains.dokka", version = "1.7.20" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+spotless = { id = "com.diffplug.spotless", version = "6.14.0" }
+mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.24.0" }
+kotlinBinaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.12.1" }
+
+[libraries]
+autoCommon = { module = "com.google.auto:auto-common", version = "1.2.1" }
+guava = { module = "com.google.guava:guava", version = "31.1-jre" }
+javapoet = "com.squareup:javapoet:1.13.0"
+
+autoService = "com.google.auto.service:auto-service-annotations:1.0.1"
+autoService-ksp = "dev.zacsweers.autoservice:auto-service-ksp:1.0.0"
+
+kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
+kotlin-annotationProcessingEmbeddable = { module = "org.jetbrains.kotlin:kotlin-annotation-processing-embeddable", version.ref = "kotlin" }
+kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
+kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
+kotlin-metadata = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.5.0" }
+
+ksp = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" }
+ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
+
+truth = { module = "com.google.truth:truth", version = "1.1.3" }
+compileTesting = { module = "com.google.testing.compile:compile-testing", version = "0.21.0" }
+jimfs = { module = "com.google.jimfs:jimfs", version = "1.2" }
+ecj = { module = "org.eclipse.jdt.core.compiler:ecj", version = "4.6.1" }
+kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kct" }
+kotlinCompileTesting-ksp = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kct" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8fad3f5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..a69d9cb
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f127cfd
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/interop/javapoet/api/javapoet.api b/interop/javapoet/api/javapoet.api
new file mode 100644
index 0000000..80ac2d9
--- /dev/null
+++ b/interop/javapoet/api/javapoet.api
@@ -0,0 +1,22 @@
+public final class com/squareup/kotlinpoet/javapoet/J2kInteropKt {
+	public static final fun toKClassName (Lcom/squareup/javapoet/ClassName;)Lcom/squareup/kotlinpoet/ClassName;
+	public static final fun toKParameterizedTypeName (Lcom/squareup/javapoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun toKTypeName (Lcom/squareup/javapoet/TypeName;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun toKTypeVariableName (Lcom/squareup/javapoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun toKWildcardTypeName (Lcom/squareup/javapoet/WildcardTypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/javapoet/K2jInteropKt {
+	public static final fun toJClassName (Lcom/squareup/kotlinpoet/ClassName;Z)Lcom/squareup/javapoet/TypeName;
+	public static synthetic fun toJClassName$default (Lcom/squareup/kotlinpoet/ClassName;ZILjava/lang/Object;)Lcom/squareup/javapoet/TypeName;
+	public static final fun toJParameterizedOrArrayTypeName (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/javapoet/TypeName;
+	public static final fun toJParameterizedTypeName (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/javapoet/ParameterizedTypeName;
+	public static final fun toJTypeName (Lcom/squareup/kotlinpoet/TypeName;Z)Lcom/squareup/javapoet/TypeName;
+	public static synthetic fun toJTypeName$default (Lcom/squareup/kotlinpoet/TypeName;ZILjava/lang/Object;)Lcom/squareup/javapoet/TypeName;
+	public static final fun toJTypeVariableName (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/javapoet/TypeVariableName;
+	public static final fun toJWildcardTypeName (Lcom/squareup/kotlinpoet/WildcardTypeName;)Lcom/squareup/javapoet/WildcardTypeName;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview : java/lang/annotation/Annotation {
+}
+
diff --git a/interop/javapoet/build.gradle.kts b/interop/javapoet/build.gradle.kts
new file mode 100644
index 0000000..9899d9c
--- /dev/null
+++ b/interop/javapoet/build.gradle.kts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+tasks.jar {
+  manifest {
+    attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.javapoet")
+  }
+}
+
+dependencies {
+  api(project(":kotlinpoet"))
+  api(libs.javapoet)
+  testImplementation(libs.kotlin.junit)
+  testImplementation(libs.truth)
+}
diff --git a/interop/javapoet/gradle.properties b/interop/javapoet/gradle.properties
new file mode 100644
index 0000000..9584afa
--- /dev/null
+++ b/interop/javapoet/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-javapoet
+POM_NAME=KotlinPoet (JavaPoet Interop)
+POM_DESCRIPTION=Extensions for interop with JavaPoet.
+POM_PACKAGING=jar
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt
new file mode 100644
index 0000000..82d7374
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BOOLEAN_ARRAY
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.BYTE_ARRAY
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.CHAR_ARRAY
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.DOUBLE_ARRAY
+import com.squareup.kotlinpoet.Dynamic
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.FLOAT_ARRAY
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.LONG_ARRAY
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.MUTABLE_LIST
+import com.squareup.kotlinpoet.MUTABLE_MAP
+import com.squareup.kotlinpoet.MUTABLE_SET
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.SHORT_ARRAY
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.U_BYTE
+import com.squareup.kotlinpoet.U_BYTE_ARRAY
+import com.squareup.kotlinpoet.U_INT
+import com.squareup.kotlinpoet.U_INT_ARRAY
+import com.squareup.kotlinpoet.U_LONG
+import com.squareup.kotlinpoet.U_LONG_ARRAY
+import com.squareup.kotlinpoet.U_SHORT
+import com.squareup.kotlinpoet.U_SHORT_ARRAY
+
+@KotlinPoetJavaPoetPreview
+public fun KClassName.toJClassName(boxIfPrimitive: Boolean = false): JTypeName {
+  return when (copy(nullable = false)) {
+    BOOLEAN -> JTypeName.BOOLEAN.boxIfPrimitive(boxIfPrimitive || isNullable)
+    BYTE, U_BYTE -> JTypeName.BYTE.boxIfPrimitive(boxIfPrimitive || isNullable)
+    CHAR -> JTypeName.CHAR.boxIfPrimitive(boxIfPrimitive || isNullable)
+    SHORT, U_SHORT -> JTypeName.SHORT.boxIfPrimitive(boxIfPrimitive || isNullable)
+    INT, U_INT -> JTypeName.INT.boxIfPrimitive(boxIfPrimitive || isNullable)
+    LONG, U_LONG -> JTypeName.LONG.boxIfPrimitive(boxIfPrimitive || isNullable)
+    FLOAT -> JTypeName.FLOAT.boxIfPrimitive(boxIfPrimitive || isNullable)
+    DOUBLE -> JTypeName.DOUBLE.boxIfPrimitive(boxIfPrimitive || isNullable)
+    ANY -> JTypeName.OBJECT
+    CHAR_SEQUENCE -> PoetInterop.CN_JAVA_CHAR_SEQUENCE
+    STRING -> PoetInterop.CN_JAVA_STRING
+    LIST, MUTABLE_LIST -> PoetInterop.CN_JAVA_LIST
+    SET, MUTABLE_SET -> PoetInterop.CN_JAVA_SET
+    MAP, MUTABLE_MAP -> PoetInterop.CN_JAVA_MAP
+    BOOLEAN_ARRAY -> ArrayTypeName.of(JTypeName.BOOLEAN)
+    BYTE_ARRAY, U_BYTE_ARRAY -> ArrayTypeName.of(JTypeName.BYTE)
+    CHAR_ARRAY -> ArrayTypeName.of(JTypeName.CHAR)
+    SHORT_ARRAY, U_SHORT_ARRAY -> ArrayTypeName.of(JTypeName.SHORT)
+    INT_ARRAY, U_INT_ARRAY -> ArrayTypeName.of(JTypeName.INT)
+    LONG_ARRAY, U_LONG_ARRAY -> ArrayTypeName.of(JTypeName.LONG)
+    FLOAT_ARRAY -> ArrayTypeName.of(JTypeName.FLOAT)
+    DOUBLE_ARRAY -> ArrayTypeName.of(JTypeName.DOUBLE)
+    ENUM -> PoetInterop.CN_JAVA_ENUM
+    else -> {
+      if (simpleNames.size == 1) {
+        JClassName.get(packageName, simpleName)
+      } else {
+        JClassName.get(packageName, simpleNames.first(), *simpleNames.drop(1).toTypedArray())
+      }
+    }
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KParameterizedTypeName.toJParameterizedOrArrayTypeName(): JTypeName {
+  return when (rawType) {
+    ARRAY -> {
+      val componentType = typeArguments.firstOrNull()?.toJTypeName()
+        ?: throw IllegalStateException("Array with no type! $this")
+      ArrayTypeName.of(componentType)
+    }
+    else -> {
+      JParameterizedTypeName.get(
+        rawType.toJClassName() as JClassName,
+        *typeArguments.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray(),
+      )
+    }
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KParameterizedTypeName.toJParameterizedTypeName(): JParameterizedTypeName {
+  check(rawType != ARRAY) {
+    "Array type! JavaPoet arrays are a custom TypeName. Use this function only for things you know are not arrays"
+  }
+  return toJParameterizedOrArrayTypeName() as JParameterizedTypeName
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KTypeVariableName.toJTypeVariableName(): JTypeVariableName {
+  return JTypeVariableName.get(name, *bounds.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray())
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KWildcardTypeName.toJWildcardTypeName(): JWildcardTypeName {
+  return if (this == STAR) {
+    JWildcardTypeName.subtypeOf(TypeName.OBJECT)
+  } else if (inTypes.size == 1) {
+    JWildcardTypeName.supertypeOf(inTypes[0].toJTypeName())
+  } else {
+    JWildcardTypeName.subtypeOf(outTypes[0].toJTypeName())
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KTypeName.toJTypeName(boxIfPrimitive: Boolean = false): JTypeName {
+  return when (this) {
+    is KClassName -> toJClassName(boxIfPrimitive)
+    Dynamic -> throw IllegalStateException("Not applicable in Java!")
+    // TODO should we return a ParameterizedTypeName of the KFunction?
+    is LambdaTypeName -> throw IllegalStateException("Not applicable in Java!")
+    is KParameterizedTypeName -> toJParameterizedOrArrayTypeName()
+    is KTypeVariableName -> toJTypeVariableName()
+    is KWildcardTypeName -> toJWildcardTypeName()
+  }
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt
new file mode 100644
index 0000000..7fcfd92
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+
+/**
+ * Indicates that a given API is part of the experimental KotlinPoet JavaPoet support and is
+ * subject to API changes.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
+public annotation class KotlinPoetJavaPoetPreview
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt
new file mode 100644
index 0000000..005e828
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+/** Various JavaPoet and KotlinPoet representations of some common types. */
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal object PoetInterop {
+  internal val CN_JAVA_CHAR_SEQUENCE = JClassName.get("java.lang", "CharSequence")
+  internal val CN_JAVA_STRING = JClassName.get("java.lang", "String")
+  internal val CN_JAVA_LIST = JClassName.get("java.util", "List")
+  internal val CN_JAVA_SET = JClassName.get("java.util", "Set")
+  internal val CN_JAVA_MAP = JClassName.get("java.util", "Map")
+  internal val CN_JAVA_ENUM = JClassName.get("java.lang", "Enum")
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt
new file mode 100644
index 0000000..7cb8fdb
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.BYTE_ARRAY
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.CHAR_ARRAY
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.DOUBLE_ARRAY
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.FLOAT_ARRAY
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.LONG_ARRAY
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.SHORT_ARRAY
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+
+@KotlinPoetJavaPoetPreview
+public fun JClassName.toKClassName(): KClassName {
+  return when (this) {
+    JTypeName.BOOLEAN.box() -> BOOLEAN
+    JTypeName.BYTE.box() -> BYTE
+    JTypeName.CHAR.box() -> CHAR
+    JTypeName.SHORT.box() -> SHORT
+    JTypeName.INT.box() -> INT
+    JTypeName.LONG.box() -> LONG
+    JTypeName.FLOAT.box() -> FLOAT
+    JTypeName.DOUBLE.box() -> DOUBLE
+    JTypeName.OBJECT -> ANY
+    PoetInterop.CN_JAVA_CHAR_SEQUENCE -> CHAR_SEQUENCE
+    PoetInterop.CN_JAVA_STRING -> STRING
+    PoetInterop.CN_JAVA_LIST -> LIST
+    PoetInterop.CN_JAVA_SET -> SET
+    PoetInterop.CN_JAVA_MAP -> MAP
+    PoetInterop.CN_JAVA_ENUM -> ENUM
+    else -> {
+      if (simpleNames().size == 1) {
+        KClassName(packageName(), simpleName())
+      } else {
+        KClassName(packageName(), simpleNames().first(), *simpleNames().drop(1).toTypedArray())
+      }
+    }
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JParameterizedTypeName.toKParameterizedTypeName(): KParameterizedTypeName {
+  return rawType.toKClassName()
+    .parameterizedBy(*typeArguments.map { it.toKTypeName() }.toTypedArray())
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JTypeVariableName.toKTypeVariableName(): KTypeVariableName {
+  return if (bounds.isEmpty()) {
+    KTypeVariableName(name)
+  } else {
+    KTypeVariableName(name, *bounds.map { it.toKTypeName() }.toTypedArray())
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JWildcardTypeName.toKWildcardTypeName(): KWildcardTypeName {
+  return if (lowerBounds.size == 1) {
+    KWildcardTypeName.consumerOf(lowerBounds.first().toKTypeName())
+  } else {
+    when (val upperBound = upperBounds[0]) {
+      TypeName.OBJECT -> STAR
+      else -> KWildcardTypeName.producerOf(upperBound.toKTypeName())
+    }
+  }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JTypeName.toKTypeName(): KTypeName {
+  return when (this) {
+    is JClassName -> toKClassName()
+    is JParameterizedTypeName -> toKParameterizedTypeName()
+    is JTypeVariableName -> toKTypeVariableName()
+    is JWildcardTypeName -> toKWildcardTypeName()
+    is ArrayTypeName -> {
+      when (componentType) {
+        JTypeName.BYTE -> BYTE_ARRAY
+        JTypeName.CHAR -> CHAR_ARRAY
+        JTypeName.SHORT -> SHORT_ARRAY
+        JTypeName.INT -> INT_ARRAY
+        JTypeName.LONG -> LONG_ARRAY
+        JTypeName.FLOAT -> FLOAT_ARRAY
+        JTypeName.DOUBLE -> DOUBLE_ARRAY
+        else -> ARRAY.parameterizedBy(componentType.toKTypeName())
+      }
+    }
+    else -> when (unboxIfBoxedPrimitive()) {
+      JTypeName.BOOLEAN -> BOOLEAN
+      JTypeName.BYTE -> BYTE
+      JTypeName.CHAR -> CHAR
+      JTypeName.SHORT -> SHORT
+      JTypeName.INT -> INT
+      JTypeName.LONG -> LONG
+      JTypeName.FLOAT -> FLOAT
+      JTypeName.DOUBLE -> DOUBLE
+      else -> error("Unrecognized type $this")
+    }
+  }
+}
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal fun JTypeName.unboxIfBoxedPrimitive(): JTypeName {
+  return if (isBoxedPrimitive) {
+    unbox()
+  } else {
+    this
+  }
+}
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal fun JTypeName.boxIfPrimitive(extraCondition: Boolean = true): JTypeName {
+  return if (extraCondition && isPrimitive && !isBoxedPrimitive) {
+    box()
+  } else {
+    this
+  }
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt
new file mode 100644
index 0000000..4982fef
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+/*
+ * Useful typealiases for colliding names
+ */
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeName = com.squareup.kotlinpoet.TypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KClassName = com.squareup.kotlinpoet.ClassName
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeVariableName = com.squareup.kotlinpoet.TypeVariableName
+
+@KotlinPoetJavaPoetPreview
+public typealias KParameterizedTypeName = com.squareup.kotlinpoet.ParameterizedTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KWildcardTypeName = com.squareup.kotlinpoet.WildcardTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeSpec = com.squareup.kotlinpoet.TypeSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias KAnnotationSpec = com.squareup.kotlinpoet.AnnotationSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeName = com.squareup.javapoet.TypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JClassName = com.squareup.javapoet.ClassName
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeVariableName = com.squareup.javapoet.TypeVariableName
+
+@KotlinPoetJavaPoetPreview
+public typealias JParameterizedTypeName = com.squareup.javapoet.ParameterizedTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JWildcardTypeName = com.squareup.javapoet.WildcardTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeSpec = com.squareup.javapoet.TypeSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias JAnnotationSpec = com.squareup.javapoet.AnnotationSpec
diff --git a/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt b/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt
new file mode 100644
index 0000000..b622e6b
--- /dev/null
+++ b/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.javapoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.U_BYTE
+import com.squareup.kotlinpoet.U_INT
+import com.squareup.kotlinpoet.U_LONG
+import com.squareup.kotlinpoet.U_SHORT
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.typeNameOf
+import org.junit.Test
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+class PoetInteropTest {
+
+  @Test
+  fun classNamesMatch() {
+    val kotlinPoetCN = PoetInteropTest::class.asClassName()
+    val javapoetCN = kotlinPoetCN.toJClassName()
+
+    assertThat(javapoetCN.toKTypeName()).isEqualTo(kotlinPoetCN)
+    assertThat(JClassName.get(PoetInteropTest::class.java)).isEqualTo(javapoetCN)
+  }
+
+  @Test
+  fun nestedClassNamesMatch() {
+    val kotlinPoetCN = PoetInteropTest::class.asClassName().nestedClass("Foo").nestedClass("Bar")
+    val javapoetCN = kotlinPoetCN.toJClassName()
+
+    assertThat(javapoetCN.toKTypeName()).isEqualTo(kotlinPoetCN)
+    assertThat(JClassName.get(PoetInteropTest::class.java).nestedClass("Foo").nestedClass("Bar"))
+      .isEqualTo(javapoetCN)
+  }
+
+  @Test
+  fun kotlinIntrinsicsMapCorrectlyToJava() {
+    // To Java
+    assertThat(LIST.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_LIST)
+    assertThat(SET.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_SET)
+    assertThat(MAP.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_MAP)
+    assertThat(STRING.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_STRING)
+    assertThat(ANY.toJTypeName()).isEqualTo(JTypeName.OBJECT)
+
+    // To Kotlin
+    assertThat(PoetInterop.CN_JAVA_LIST.toKTypeName()).isEqualTo(LIST)
+    assertThat(PoetInterop.CN_JAVA_SET.toKTypeName()).isEqualTo(SET)
+    assertThat(PoetInterop.CN_JAVA_MAP.toKTypeName()).isEqualTo(MAP)
+    assertThat(PoetInterop.CN_JAVA_STRING.toKTypeName()).isEqualTo(STRING)
+    assertThat(JTypeName.OBJECT.toKTypeName()).isEqualTo(ANY)
+  }
+
+  @Test
+  fun boxIfPrimitiveRequestReturnsBoxedPrimitive() {
+    assertThat(BOOLEAN.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.BOOLEAN.box())
+    assertThat(BYTE.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.BYTE.box())
+    assertThat(CHAR.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.CHAR.box())
+    assertThat(SHORT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.SHORT.box())
+    assertThat(INT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.INT.box())
+    assertThat(LONG.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.LONG.box())
+    assertThat(FLOAT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.FLOAT.box())
+    assertThat(DOUBLE.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.DOUBLE.box())
+  }
+
+  @Test
+  fun primitivesAreUnboxedByDefault() {
+    assertThat(BOOLEAN.toJTypeName()).isEqualTo(JTypeName.BOOLEAN)
+    assertThat(BYTE.toJTypeName()).isEqualTo(JTypeName.BYTE)
+    assertThat(CHAR.toJTypeName()).isEqualTo(JTypeName.CHAR)
+    assertThat(SHORT.toJTypeName()).isEqualTo(JTypeName.SHORT)
+    assertThat(INT.toJTypeName()).isEqualTo(JTypeName.INT)
+    assertThat(LONG.toJTypeName()).isEqualTo(JTypeName.LONG)
+    assertThat(FLOAT.toJTypeName()).isEqualTo(JTypeName.FLOAT)
+    assertThat(DOUBLE.toJTypeName()).isEqualTo(JTypeName.DOUBLE)
+  }
+
+  @Test
+  fun nullablePrimitiveBoxedByDefault() {
+    assertThat(BOOLEAN.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.BOOLEAN.box())
+    assertThat(BYTE.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.BYTE.box())
+    assertThat(CHAR.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.CHAR.box())
+    assertThat(SHORT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.SHORT.box())
+    assertThat(INT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.INT.box())
+    assertThat(LONG.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.LONG.box())
+    assertThat(FLOAT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.FLOAT.box())
+    assertThat(DOUBLE.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.DOUBLE.box())
+  }
+
+  @Test
+  fun arrayTypesConversion() {
+    assertThat(ARRAY.parameterizedBy(INT).toJParameterizedOrArrayTypeName())
+      .isEqualTo(ArrayTypeName.of(JTypeName.INT))
+    assertThat(ARRAY.parameterizedBy(INT.copy(nullable = true)).toJParameterizedOrArrayTypeName())
+      .isEqualTo(ArrayTypeName.of(JTypeName.INT.box()))
+    assertThat(ArrayTypeName.of(JTypeName.INT).toKTypeName()).isEqualTo(INT_ARRAY)
+    assertThat(ArrayTypeName.of(JTypeName.INT.box()).toKTypeName())
+      .isEqualTo(ARRAY.parameterizedBy(INT))
+  }
+
+  class GenericType<T>
+
+  @Test
+  fun wildcards() {
+    val inKType = typeNameOf<GenericType<in String>>()
+    val superJType = JParameterizedTypeName.get(
+      JClassName.get(GenericType::class.java),
+      JWildcardTypeName.supertypeOf(String::class.java),
+    )
+    assertThat(inKType.toJTypeName()).isEqualTo(superJType)
+    assertThat(superJType.toKTypeName()).isEqualTo(inKType)
+
+    val outKType = typeNameOf<GenericType<out String>>()
+    val extendsJType = JParameterizedTypeName.get(
+      JClassName.get(GenericType::class.java),
+      JWildcardTypeName.subtypeOf(String::class.java),
+    )
+    assertThat(outKType.toJTypeName()).isEqualTo(extendsJType)
+    assertThat(extendsJType.toKTypeName()).isEqualTo(outKType)
+
+    val star = typeNameOf<GenericType<*>>()
+    val extendsObjectJType = JParameterizedTypeName.get(
+      JClassName.get(GenericType::class.java),
+      JWildcardTypeName.subtypeOf(JTypeName.OBJECT),
+    )
+    assertThat(star.toJTypeName()).isEqualTo(extendsObjectJType)
+    assertThat(extendsObjectJType.toKTypeName()).isEqualTo(star)
+    assertThat(STAR.toJTypeName()).isEqualTo(JWildcardTypeName.subtypeOf(JTypeName.OBJECT))
+    assertThat(JWildcardTypeName.subtypeOf(JTypeName.OBJECT).toKTypeName()).isEqualTo(STAR)
+  }
+
+  @Test
+  fun complex() {
+    val complexType = typeNameOf<Map<String?, List<MutableMap<Int, IntArray>>>>()
+    val jType = JParameterizedTypeName.get(
+      JClassName.get(Map::class.java),
+      JClassName.get(String::class.java),
+      JParameterizedTypeName.get(
+        JClassName.get(List::class.java),
+        JParameterizedTypeName.get(
+          JClassName.get(Map::class.java),
+          JClassName.INT.box(),
+          ArrayTypeName.of(JClassName.INT),
+        ),
+      ),
+    )
+    assertThat(complexType.toJTypeName()).isEqualTo(jType)
+
+    assertThat(jType.toKTypeName())
+      .isEqualTo(typeNameOf<Map<String, List<MutableMap<Int, IntArray>>>>())
+  }
+
+  @Test
+  fun uTypesAreJustNormalTypesInJava() {
+    assertThat(U_BYTE.toJTypeName()).isEqualTo(JTypeName.BYTE)
+    assertThat(U_SHORT.toJTypeName()).isEqualTo(JTypeName.SHORT)
+    assertThat(U_INT.toJTypeName()).isEqualTo(JTypeName.INT)
+    assertThat(U_LONG.toJTypeName()).isEqualTo(JTypeName.LONG)
+  }
+
+  @Test
+  fun enums() {
+    assertThat(ENUM.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_ENUM)
+    assertThat(PoetInterop.CN_JAVA_ENUM.toKTypeName()).isEqualTo(ENUM)
+  }
+}
diff --git a/interop/kotlinx-metadata/api/kotlinx-metadata.api b/interop/kotlinx-metadata/api/kotlinx-metadata.api
new file mode 100644
index 0000000..96f47c0
--- /dev/null
+++ b/interop/kotlinx-metadata/api/kotlinx-metadata.api
@@ -0,0 +1,369 @@
+public final class com/squareup/kotlinpoet/metadata/FlagsKt {
+	public static final fun getDeclaresDefaultValue (Lkotlinx/metadata/KmValueParameter;)Z
+	public static final fun getGetterPropertyAccessorFlags (Lkotlinx/metadata/KmProperty;)Ljava/util/Set;
+	public static final fun getHasAnnotations (I)Z
+	public static final fun getHasConstant (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun getHasGetter (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun getHasSetter (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun getPropertyAccessorFlags (I)Ljava/util/Set;
+	public static final fun getSetterPropertyAccessorFlags (Lkotlinx/metadata/KmProperty;)Ljava/util/Set;
+	public static final fun isAbstract (I)Z
+	public static final fun isAnnotation (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isAnnotationClass (I)Z
+	public static final fun isClass (I)Z
+	public static final fun isClass (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isCompanionObject (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isCompanionObjectClass (I)Z
+	public static final fun isConst (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isCrossInline (Lkotlinx/metadata/KmValueParameter;)Z
+	public static final fun isData (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isDataClass (I)Z
+	public static final fun isDeclaration (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isDeclaration (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isDeclarationFunction (I)Z
+	public static final fun isDelegated (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isDelegation (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isDelegation (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isDelegationFunction (I)Z
+	public static final fun isEnum (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isEnumClass (I)Z
+	public static final fun isEnumEntry (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isEnumEntryClass (I)Z
+	public static final fun isExpect (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isExpect (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isExpect (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isExpectClass (I)Z
+	public static final fun isExpectFunction (I)Z
+	public static final fun isExternal (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isExternal (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isExternal (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isExternalClass (I)Z
+	public static final fun isExternalFunction (I)Z
+	public static final fun isFakeOverride (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isFakeOverride (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isFakeOverrideFunction (I)Z
+	public static final fun isFakeOverrideProperty (I)Z
+	public static final fun isFinal (I)Z
+	public static final fun isFun (I)Z
+	public static final fun isFun (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isInfix (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isInfixFunction (I)Z
+	public static final fun isInline (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isInlineFunction (I)Z
+	public static final fun isInner (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isInnerClass (I)Z
+	public static final fun isInterface (I)Z
+	public static final fun isInterface (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isInternal (I)Z
+	public static final fun isLateinit (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isLocal (I)Z
+	public static final fun isNoInline (Lkotlinx/metadata/KmValueParameter;)Z
+	public static final fun isNullable (Lkotlinx/metadata/KmType;)Z
+	public static final fun isNullableType (I)Z
+	public static final fun isObject (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isObjectClass (I)Z
+	public static final fun isOpen (I)Z
+	public static final fun isOperator (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isOperatorFunction (I)Z
+	public static final fun isPrimary (Lkotlinx/metadata/KmConstructor;)Z
+	public static final fun isPrimaryConstructor (I)Z
+	public static final fun isPrivate (I)Z
+	public static final fun isPrivate_to_this (I)Z
+	public static final fun isPropertyAccessorExternal (I)Z
+	public static final fun isPropertyAccessorInline (I)Z
+	public static final fun isPropertyAccessorNotDefault (I)Z
+	public static final fun isProtected (I)Z
+	public static final fun isPublic (I)Z
+	public static final fun isReified (Lkotlinx/metadata/KmTypeParameter;)Z
+	public static final fun isSealed (I)Z
+	public static final fun isSecondary (Lkotlinx/metadata/KmConstructor;)Z
+	public static final fun isSuspend (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isSuspend (Lkotlinx/metadata/KmType;)Z
+	public static final fun isSuspendFunction (I)Z
+	public static final fun isSuspendType (I)Z
+	public static final fun isSynthesized (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isSynthesized (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isSynthesizedFunction (I)Z
+	public static final fun isTailRec (Lkotlinx/metadata/KmFunction;)Z
+	public static final fun isTailRecFunction (I)Z
+	public static final fun isVal (Lkotlinx/metadata/KmProperty;)Z
+	public static final fun isValue (Lkotlinx/metadata/KmClass;)Z
+	public static final fun isValueClass (I)Z
+	public static final fun isVar (Lkotlinx/metadata/KmProperty;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/KotlinPoetMetadata {
+	public static final fun readKotlinClassMetadata (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata;
+	public static final fun toKmClass (Ljava/lang/Class;)Lkotlinx/metadata/KmClass;
+	public static final fun toKmClass (Ljavax/lang/model/element/TypeElement;)Lkotlinx/metadata/KmClass;
+	public static final fun toKmClass (Lkotlin/Metadata;)Lkotlinx/metadata/KmClass;
+	public static final fun toKmClass (Lkotlin/reflect/KClass;)Lkotlinx/metadata/KmClass;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/metadata/KotlinPoetMetadataPreview : java/lang/annotation/Annotation {
+}
+
+public final class com/squareup/kotlinpoet/metadata/PropertyAccessorFlag : java/lang/Enum {
+	public static final field IS_EXTERNAL Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+	public static final field IS_INLINE Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+	public static final field IS_NOT_DEFAULT Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+	public static fun values ()[Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector : com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+	public static final field Companion Lcom/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector$Companion;
+	public synthetic fun <init> (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+	public static final fun create (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+	public fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+	public fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+	public fun getSupportsNonRuntimeRetainedAnnotations ()Z
+	public fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+	public fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector$Companion {
+	public final fun create (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector : com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+	public static final field Companion Lcom/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion;
+	public synthetic fun <init> (Ljava/lang/ClassLoader;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+	public static final fun create (Ljava/lang/ClassLoader;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+	public fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+	public fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+	public fun getSupportsNonRuntimeRetainedAnnotations ()Z
+	public fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+	public fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion {
+	public final fun create (Ljava/lang/ClassLoader;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+	public static synthetic fun create$default (Lcom/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ClassData : com/squareup/kotlinpoet/metadata/specs/ContainerData {
+	public fun <init> (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+	public final fun component1 ()Lkotlinx/metadata/KmClass;
+	public final fun component2 ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun component3 ()Ljava/util/Collection;
+	public final fun component4 ()Ljava/util/Map;
+	public final fun component5 ()Ljava/util/Map;
+	public final fun component6 ()Ljava/util/Map;
+	public final fun copy (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/squareup/kotlinpoet/metadata/specs/ClassData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/ClassData;Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ClassData;
+	public fun equals (Ljava/lang/Object;)Z
+	public fun getAnnotations ()Ljava/util/Collection;
+	public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun getConstructors ()Ljava/util/Map;
+	public fun getDeclarationContainer ()Lkotlinx/metadata/KmClass;
+	public synthetic fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+	public fun getMethods ()Ljava/util/Map;
+	public fun getProperties ()Ljava/util/Map;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+	public abstract fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+	public abstract fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+	public abstract fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+	public abstract fun getSupportsNonRuntimeRetainedAnnotations ()Z
+	public abstract fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+	public abstract fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ClassInspectorKt {
+	public static final fun classFor (Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmClass;
+	public static final fun containerData (Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ConstructorData {
+	public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData$Companion;
+	public fun <init> (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;)V
+	public final fun component2 ()Ljava/util/Map;
+	public final fun component3 ()Z
+	public final fun component4 ()Ljava/util/Set;
+	public final fun component5 ()Ljava/util/List;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;)Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAllAnnotations ()Ljava/util/Collection;
+	public final fun getExceptions ()Ljava/util/List;
+	public final fun getJvmModifiers ()Ljava/util/Set;
+	public final fun getParameterAnnotations ()Ljava/util/Map;
+	public fun hashCode ()I
+	public final fun isSynthetic ()Z
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ConstructorData$Companion {
+	public final fun getEMPTY ()Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/ContainerData {
+	public abstract fun getAnnotations ()Ljava/util/Collection;
+	public abstract fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+	public abstract fun getMethods ()Ljava/util/Map;
+	public abstract fun getProperties ()Ljava/util/Map;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/EnumEntryData {
+	public fun <init> (Lkotlinx/metadata/KmClass;Ljava/util/Collection;)V
+	public final fun component1 ()Lkotlinx/metadata/KmClass;
+	public final fun component2 ()Ljava/util/Collection;
+	public final fun copy (Lkotlinx/metadata/KmClass;Ljava/util/Collection;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;Lkotlinx/metadata/KmClass;Ljava/util/Collection;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAnnotations ()Ljava/util/Collection;
+	public final fun getDeclarationContainer ()Lkotlinx/metadata/KmClass;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FieldData {
+	public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/FieldData$Companion;
+	public fun <init> (Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;)V
+	public final fun component2 ()Z
+	public final fun component3 ()Ljava/util/Set;
+	public final fun component4 ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun copy (Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAllAnnotations ()Ljava/util/Collection;
+	public final fun getConstant ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getJvmModifiers ()Ljava/util/Set;
+	public fun hashCode ()I
+	public final fun isSynthetic ()Z
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FieldData$Companion {
+	public final fun getSYNTHETIC ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FileData : com/squareup/kotlinpoet/metadata/specs/ContainerData {
+	public fun <init> (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)V
+	public synthetic fun <init> (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Lkotlinx/metadata/KmPackage;
+	public final fun component2 ()Ljava/util/Collection;
+	public final fun component3 ()Ljava/util/Map;
+	public final fun component4 ()Ljava/util/Map;
+	public final fun component5 ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun component6 ()Ljava/lang/String;
+	public final fun copy (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/FileData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/FileData;Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/FileData;
+	public fun equals (Ljava/lang/Object;)Z
+	public fun getAnnotations ()Ljava/util/Collection;
+	public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+	public synthetic fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+	public fun getDeclarationContainer ()Lkotlinx/metadata/KmPackage;
+	public final fun getFileName ()Ljava/lang/String;
+	public final fun getJvmName ()Ljava/lang/String;
+	public fun getMethods ()Ljava/util/Map;
+	public fun getProperties ()Ljava/util/Map;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public class com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier : java/lang/Enum, com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+	public static final field STATIC Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+	public static final field TRANSIENT Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+	public static final field VOLATILE Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+	public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+	public static fun values ()[Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+}
+
+public class com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier : java/lang/Enum, com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+	public static final field DEFAULT Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+	public static final field STATIC Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+	public static final field SYNCHRONIZED Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+	public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+	public static fun values ()[Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+	public abstract fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/JvmModifier$DefaultImpls {
+	public static fun annotationSpec (Lcom/squareup/kotlinpoet/metadata/specs/JvmModifier;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/KmTypesKt {
+	public static final fun isExtensionType (Lkotlinx/metadata/KmType;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs {
+	public static final fun getPackageName (Ljavax/lang/model/element/Element;)Ljava/lang/String;
+	public static final fun toFileSpec (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static final fun toFileSpec (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static final fun toFileSpec (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static final fun toFileSpec (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static final fun toFileSpec (Lkotlinx/metadata/KmPackage;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static synthetic fun toFileSpec$default (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static synthetic fun toFileSpec$default (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static synthetic fun toFileSpec$default (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static synthetic fun toFileSpec$default (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+	public static final fun toTypeSpec (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static final fun toTypeSpec (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static final fun toTypeSpec (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static final fun toTypeSpec (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static synthetic fun toTypeSpec$default (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static synthetic fun toTypeSpec$default (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static synthetic fun toTypeSpec$default (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+	public static synthetic fun toTypeSpec$default (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/MethodData {
+	public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/MethodData$Companion;
+	public fun <init> (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;)V
+	public final fun allAnnotations (Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;Z)Ljava/util/Collection;
+	public static synthetic fun allAnnotations$default (Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;ZILjava/lang/Object;)Ljava/util/Collection;
+	public final fun component2 ()Ljava/util/Map;
+	public final fun component3 ()Z
+	public final fun component4 ()Ljava/util/Set;
+	public final fun component5 ()Z
+	public final fun component6 ()Ljava/util/List;
+	public final fun copy (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;)Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getExceptions ()Ljava/util/List;
+	public final fun getJvmModifiers ()Ljava/util/Set;
+	public final fun getParameterAnnotations ()Ljava/util/Map;
+	public fun hashCode ()I
+	public final fun isOverride ()Z
+	public final fun isSynthetic ()Z
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/MethodData$Companion {
+	public final fun getEMPTY ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public final fun getSYNTHETIC ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/PropertyData {
+	public fun <init> (Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Z)V
+	public final fun component2 ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+	public final fun component3 ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public final fun component4 ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public final fun component5 ()Z
+	public final fun copy (Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Z)Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAllAnnotations ()Ljava/util/Collection;
+	public final fun getFieldData ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+	public final fun getGetterData ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public final fun getSetterData ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+	public fun hashCode ()I
+	public final fun isJvmField ()Z
+	public final fun isOverride ()Z
+	public fun toString ()Ljava/lang/String;
+}
+
diff --git a/interop/kotlinx-metadata/build.gradle.kts b/interop/kotlinx-metadata/build.gradle.kts
new file mode 100644
index 0000000..df75e1a
--- /dev/null
+++ b/interop/kotlinx-metadata/build.gradle.kts
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+tasks.jar {
+  manifest {
+    attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.metadata")
+  }
+}
+
+tasks.compileTestKotlin {
+  kotlinOptions {
+    freeCompilerArgs = listOf(
+      "-Xjvm-default=all",
+      "-opt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview",
+    )
+  }
+}
+
+dependencies {
+  implementation(libs.autoCommon)
+  implementation(libs.guava)
+  api(libs.kotlin.metadata)
+  api(project(":kotlinpoet"))
+
+  testImplementation(libs.kotlin.junit)
+  testImplementation(libs.truth)
+  testImplementation(libs.compileTesting)
+  testImplementation(libs.kotlinCompileTesting)
+  testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+  testImplementation(libs.kotlin.compilerEmbeddable)
+}
diff --git a/interop/kotlinx-metadata/gradle.properties b/interop/kotlinx-metadata/gradle.properties
new file mode 100644
index 0000000..dba6fb4
--- /dev/null
+++ b/interop/kotlinx-metadata/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-metadata
+POM_NAME=KotlinPoet (Kotlin Metadata Extensions)
+POM_DESCRIPTION=Extensions for reading Kotlin metadata from Metadata annotations.
+POM_PACKAGING=jar
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
new file mode 100644
index 0000000..4745dda
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmValueParameter
+
+// Common flags for any element with flags.
+@KotlinPoetMetadataPreview
+public val Flags.hasAnnotations: Boolean get() = Flag.HAS_ANNOTATIONS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isAbstract: Boolean get() = Flag.IS_ABSTRACT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFinal: Boolean get() = Flag.IS_FINAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInternal: Boolean get() = Flag.IS_INTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isLocal: Boolean get() = Flag.IS_LOCAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOpen: Boolean get() = Flag.IS_OPEN(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate: Boolean get() = Flag.IS_PRIVATE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate_to_this: Boolean get() = Flag.IS_PRIVATE_TO_THIS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isProtected: Boolean get() = Flag.IS_PROTECTED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPublic: Boolean get() = Flag.IS_PUBLIC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSealed: Boolean get() = Flag.IS_SEALED(this)
+
+// Type flags.
+@KotlinPoetMetadataPreview
+public val Flags.isNullableType: Boolean get() = Flag.Type.IS_NULLABLE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendType: Boolean get() = Flag.Type.IS_SUSPEND(this)
+
+// Class flags.
+@KotlinPoetMetadataPreview
+public val Flags.isAnnotationClass: Boolean get() = Flag.Class.IS_ANNOTATION_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isClass: Boolean get() = Flag.Class.IS_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isCompanionObjectClass: Boolean get() = Flag.Class.IS_COMPANION_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDataClass: Boolean get() = Flag.Class.IS_DATA(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumClass: Boolean get() = Flag.Class.IS_ENUM_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumEntryClass: Boolean get() = Flag.Class.IS_ENUM_ENTRY(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectClass: Boolean get() = Flag.Class.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalClass: Boolean get() = Flag.Class.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isValueClass: Boolean get() = Flag.Class.IS_VALUE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInnerClass: Boolean get() = Flag.Class.IS_INNER(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isObjectClass: Boolean get() = Flag.Class.IS_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInterface: Boolean get() = Flag.Class.IS_INTERFACE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFun: Boolean get() = Flag.Class.IS_FUN(this)
+
+@KotlinPoetMetadataPreview
+public val KmClass.isAnnotation: Boolean get() = flags.isAnnotationClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isClass: Boolean get() = flags.isClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isCompanionObject: Boolean get() = flags.isCompanionObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isData: Boolean get() = flags.isDataClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnum: Boolean get() = flags.isEnumClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnumEntry: Boolean get() = flags.isEnumEntryClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExpect: Boolean get() = flags.isExpectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExternal: Boolean get() = flags.isExternalClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isValue: Boolean get() = flags.isValueClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInner: Boolean get() = flags.isInnerClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isObject: Boolean get() = flags.isObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInterface: Boolean get() = flags.isInterface
+
+@KotlinPoetMetadataPreview
+public val KmClass.isFun: Boolean get() = flags.isFun
+
+@KotlinPoetMetadataPreview
+public val KmType.isSuspend: Boolean get() = flags.isSuspendType
+
+@KotlinPoetMetadataPreview
+public val KmType.isNullable: Boolean get() = flags.isNullableType
+
+// Constructor flags.
+@KotlinPoetMetadataPreview
+public val Flags.isPrimaryConstructor: Boolean get() = !Flag.Constructor.IS_SECONDARY(this)
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isPrimary: Boolean get() = flags.isPrimaryConstructor
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isSecondary: Boolean get() = !isPrimary
+
+// Function flags.
+@KotlinPoetMetadataPreview
+public val Flags.isDeclarationFunction: Boolean get() = Flag.Function.IS_DECLARATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideFunction: Boolean get() = Flag.Function.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDelegationFunction: Boolean get() = Flag.Function.IS_DELEGATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSynthesizedFunction: Boolean get() = Flag.Function.IS_SYNTHESIZED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOperatorFunction: Boolean get() = Flag.Function.IS_OPERATOR(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInfixFunction: Boolean get() = Flag.Function.IS_INFIX(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInlineFunction: Boolean get() = Flag.Function.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isTailRecFunction: Boolean get() = Flag.Function.IS_TAILREC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalFunction: Boolean get() = Flag.Function.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendFunction: Boolean get() = Flag.Function.IS_SUSPEND(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectFunction: Boolean get() = Flag.Function.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDeclaration: Boolean get() = flags.isDeclarationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isFakeOverride: Boolean get() = flags.isFakeOverrideFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDelegation: Boolean get() = flags.isDelegationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSynthesized: Boolean get() = flags.isSynthesizedFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isOperator: Boolean get() = flags.isOperatorFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInfix: Boolean get() = flags.isInfixFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInline: Boolean get() = flags.isInlineFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isTailRec: Boolean get() = flags.isTailRecFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExternal: Boolean get() = flags.isExternalFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSuspend: Boolean get() = flags.isSuspendFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExpect: Boolean get() = flags.isExpectFunction
+
+// Parameter flags.
+@KotlinPoetMetadataPreview
+public val KmValueParameter.declaresDefaultValue: Boolean get() =
+  Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isCrossInline: Boolean get() = Flag.ValueParameter.IS_CROSSINLINE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isNoInline: Boolean get() = Flag.ValueParameter.IS_NOINLINE(flags)
+
+// Property flags.
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideProperty: Boolean get() = Flag.Property.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasConstant: Boolean get() = Flag.Property.HAS_CONSTANT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasGetter: Boolean get() = Flag.Property.HAS_GETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasSetter: Boolean get() = Flag.Property.HAS_SETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isConst: Boolean get() = Flag.Property.IS_CONST(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDeclaration: Boolean get() = Flag.Property.IS_DECLARATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegated: Boolean get() = Flag.Property.IS_DELEGATED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegation: Boolean get() = Flag.Property.IS_DELEGATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExpect: Boolean get() = Flag.Property.IS_EXPECT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExternal: Boolean get() = Flag.Property.IS_EXTERNAL(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isFakeOverride: Boolean get() = flags.isFakeOverrideProperty
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isLateinit: Boolean get() = Flag.Property.IS_LATEINIT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isSynthesized: Boolean get() = Flag.Property.IS_SYNTHESIZED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVar: Boolean get() = Flag.Property.IS_VAR(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVal: Boolean get() = !isVar
+
+// Property Accessor Flags
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorExternal: Boolean
+  get() = Flag.PropertyAccessor.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorInline: Boolean
+  get() = Flag.PropertyAccessor.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorNotDefault: Boolean
+  get() = Flag.PropertyAccessor.IS_NOT_DEFAULT(this)
+
+// TypeParameter flags.
+@KotlinPoetMetadataPreview
+public val KmTypeParameter.isReified: Boolean get() = Flag.TypeParameter.IS_REIFIED(flags)
+
+// Property Accessor Flags
+public enum class PropertyAccessorFlag {
+  IS_EXTERNAL,
+  IS_INLINE,
+  IS_NOT_DEFAULT,
+}
+
+@KotlinPoetMetadataPreview
+public val KmProperty.setterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+  get() = setterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val KmProperty.getterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+  get() = getterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val Flags.propertyAccessorFlags: Set<PropertyAccessorFlag>
+  get() = setOf {
+    if (Flag.PropertyAccessor.IS_EXTERNAL(this@propertyAccessorFlags)) {
+      add(PropertyAccessorFlag.IS_EXTERNAL)
+    }
+    if (Flag.PropertyAccessor.IS_INLINE(this@propertyAccessorFlags)) {
+      add(PropertyAccessorFlag.IS_INLINE)
+    }
+    if (Flag.PropertyAccessor.IS_NOT_DEFAULT(this@propertyAccessorFlags)) {
+      add(PropertyAccessorFlag.IS_NOT_DEFAULT)
+    }
+  }
+
+internal inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+  return mutableSetOf<E>().apply(body).toSet()
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
new file mode 100644
index 0000000..6cb3c91
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+@file:JvmName("KotlinPoetMetadata")
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import javax.lang.model.element.TypeElement
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.reflect.KClass
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Indicates that a given API is part of the experimental KotlinPoet metadata support. This exists
+ * because kotlinx-metadata is not a stable API, and will remain in place until it is.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY)
+public annotation class KotlinPoetMetadataPreview
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toKmClass(): KmClass = java.toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] type. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+@KotlinPoetMetadataPreview
+public fun Metadata.toKmClass(): KmClass {
+  return toKotlinClassMetadata<KotlinClassMetadata.Class>()
+    .toKmClass()
+}
+
+@KotlinPoetMetadataPreview
+public inline fun <reified T : KotlinClassMetadata> Metadata.toKotlinClassMetadata(): T {
+  val expectedType = T::class
+  val metadata = readKotlinClassMetadata()
+  return when (expectedType) {
+    KotlinClassMetadata.Class::class -> {
+      check(metadata is KotlinClassMetadata.Class)
+      metadata as T
+    }
+    KotlinClassMetadata.FileFacade::class -> {
+      check(metadata is KotlinClassMetadata.FileFacade)
+      metadata as T
+    }
+    KotlinClassMetadata.SyntheticClass::class ->
+      throw UnsupportedOperationException("SyntheticClass isn't supported yet!")
+    KotlinClassMetadata.MultiFileClassFacade::class ->
+      throw UnsupportedOperationException("MultiFileClassFacade isn't supported yet!")
+    KotlinClassMetadata.MultiFileClassPart::class ->
+      throw UnsupportedOperationException("MultiFileClassPart isn't supported yet!")
+    KotlinClassMetadata.Unknown::class ->
+      throw RuntimeException("Recorded unknown metadata type! $metadata")
+    else -> TODO("Unrecognized KotlinClassMetadata type: $expectedType")
+  }
+}
+
+/**
+ * Returns the [KotlinClassMetadata] this represents. In general you should only use this function
+ * when you don't know what the underlying [KotlinClassMetadata] subtype is, otherwise you should
+ * use one of the more direct functions like [toKmClass].
+ */
+@KotlinPoetMetadataPreview
+public fun Metadata.readKotlinClassMetadata(): KotlinClassMetadata {
+  val metadata = KotlinClassMetadata.read(asClassHeader())
+  checkNotNull(metadata) {
+    "Could not parse metadata! Try bumping kotlinpoet and/or kotlinx-metadata version."
+  }
+  return metadata
+}
+
+private inline fun readMetadata(lookup: ((Class<Metadata>) -> Metadata?)): Metadata {
+  return checkNotNull(lookup.invoke(Metadata::class.java)) {
+    "No Metadata annotation found! Must be Kotlin code built with the standard library on the classpath."
+  }
+}
+
+private fun Metadata.asClassHeader(): KotlinClassHeader {
+  return KotlinClassHeader(
+    kind = kind,
+    metadataVersion = metadataVersion,
+    data1 = data1,
+    data2 = data2,
+    extraString = extraString,
+    packageName = packageName,
+    extraInt = extraInt,
+  )
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
new file mode 100644
index 0000000..d9caa83
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.COLLECTION
+import com.squareup.kotlinpoet.COMPARABLE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.ITERABLE
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_COLLECTION
+import com.squareup.kotlinpoet.MUTABLE_ITERABLE
+import com.squareup.kotlinpoet.MUTABLE_LIST
+import com.squareup.kotlinpoet.MUTABLE_MAP
+import com.squareup.kotlinpoet.MUTABLE_MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_SET
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import java.util.Collections
+import java.util.TreeSet
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.isLocal
+import org.jetbrains.annotations.NotNull
+import org.jetbrains.annotations.Nullable
+
+@KotlinPoetMetadataPreview
+internal object ClassInspectorUtil {
+  val JVM_NAME: ClassName = JvmName::class.asClassName()
+  private val JVM_FIELD = JvmField::class.asClassName()
+  internal val JVM_FIELD_SPEC = AnnotationSpec.builder(JVM_FIELD).build()
+  internal val JVM_SYNTHETIC = JvmSynthetic::class.asClassName()
+  internal val JVM_SYNTHETIC_SPEC = AnnotationSpec.builder(JVM_SYNTHETIC).build()
+  internal val JAVA_DEPRECATED = java.lang.Deprecated::class.asClassName()
+  private val JVM_TRANSIENT = Transient::class.asClassName()
+  private val JVM_VOLATILE = Volatile::class.asClassName()
+  private val IMPLICIT_FIELD_ANNOTATIONS = setOf(
+    JVM_FIELD,
+    JVM_TRANSIENT,
+    JVM_VOLATILE,
+  )
+  private val NOT_NULL = NotNull::class.asClassName()
+  private val NULLABLE = Nullable::class.asClassName()
+  private val EXTENSION_FUNCTION_TYPE = ExtensionFunctionType::class.asClassName()
+  private val KOTLIN_INTRINSIC_ANNOTATIONS = setOf(
+    NOT_NULL,
+    NULLABLE,
+    EXTENSION_FUNCTION_TYPE,
+  )
+
+  val KOTLIN_INTRINSIC_INTERFACES: Set<ClassName> = setOf(
+    CHAR_SEQUENCE,
+    COMPARABLE,
+    ITERABLE,
+    COLLECTION,
+    LIST,
+    SET,
+    MAP,
+    MAP_ENTRY,
+    MUTABLE_ITERABLE,
+    MUTABLE_COLLECTION,
+    MUTABLE_LIST,
+    MUTABLE_SET,
+    MUTABLE_MAP,
+    MUTABLE_MAP_ENTRY,
+  )
+
+  private val KOTLIN_NULLABILITY_ANNOTATIONS = setOf(
+    "org.jetbrains.annotations.NotNull",
+    "org.jetbrains.annotations.Nullable",
+  )
+
+  fun filterOutNullabilityAnnotations(
+    annotations: List<AnnotationSpec>,
+  ): List<AnnotationSpec> {
+    return annotations.filterNot {
+      val typeName = it.typeName
+      return@filterNot typeName is ClassName &&
+        typeName.canonicalName in KOTLIN_NULLABILITY_ANNOTATIONS
+    }
+  }
+
+  /** @return a [CodeBlock] representation of a [literal] value. */
+  fun codeLiteralOf(literal: Any): CodeBlock {
+    return when (literal) {
+      is String -> CodeBlock.of("%S", literal)
+      is Long -> CodeBlock.of("%LL", literal)
+      is Float -> CodeBlock.of("%LF", literal)
+      else -> CodeBlock.of("%L", literal)
+    }
+  }
+
+  /**
+   * Infers if [property] is a jvm field and should be annotated as such given the input
+   * parameters.
+   */
+  fun computeIsJvmField(
+    property: KmProperty,
+    classInspector: ClassInspector,
+    isCompanionObject: Boolean,
+    hasGetter: Boolean,
+    hasSetter: Boolean,
+    hasField: Boolean,
+  ): Boolean {
+    return if (!hasGetter &&
+      !hasSetter &&
+      hasField &&
+      !property.isConst
+    ) {
+      !(classInspector.supportsNonRuntimeRetainedAnnotations && !isCompanionObject)
+    } else {
+      false
+    }
+  }
+
+  /**
+   * @return a new collection of [AnnotationSpecs][AnnotationSpec] with sorting and de-duping
+   *         input annotations from [body].
+   */
+  fun createAnnotations(
+    siteTarget: UseSiteTarget? = null,
+    body: MutableCollection<AnnotationSpec>.() -> Unit,
+  ): Collection<AnnotationSpec> {
+    val result = mutableSetOf<AnnotationSpec>()
+      .apply(body)
+      .filterNot { spec ->
+        spec.typeName in KOTLIN_INTRINSIC_ANNOTATIONS
+      }
+    val withUseSiteTarget = if (siteTarget != null) {
+      result.map {
+        if (!(siteTarget == FIELD && it.typeName in IMPLICIT_FIELD_ANNOTATIONS)) {
+          // Some annotations are implicitly only for FIELD, so don't emit those site targets
+          it.toBuilder().useSiteTarget(siteTarget).build()
+        } else {
+          it
+        }
+      }
+    } else {
+      result
+    }
+
+    val sorted = withUseSiteTarget.toTreeSet()
+
+    return Collections.unmodifiableCollection(sorted)
+  }
+
+  /**
+   * @return a [@Throws][Throws] [AnnotationSpec] representation of a given collection of
+   *         [exceptions].
+   */
+  fun createThrowsSpec(
+    exceptions: Collection<TypeName>,
+    useSiteTarget: UseSiteTarget? = null,
+  ): AnnotationSpec {
+    return AnnotationSpec.builder(Throws::class)
+      .addMember(
+        "exceptionClasses = %L",
+        exceptions.map { CodeBlock.of("%T::class", it) }
+          .joinToCode(prefix = "[", suffix = "]"),
+      )
+      .useSiteTarget(useSiteTarget)
+      .build()
+  }
+
+  /**
+   * Best guesses a [ClassName] as represented in Metadata's [kotlinx.metadata.ClassName], where
+   * package names in this name are separated by '/' and class names are separated by '.'.
+   *
+   * For example: `"org/foo/bar/Baz.Nested"`.
+   *
+   * Local classes are prefixed with ".", but for KotlinPoetMetadataSpecs' use case we don't deal
+   * with those.
+   */
+  fun createClassName(kotlinMetadataName: String): ClassName {
+    require(!kotlinMetadataName.isLocal) {
+      "Local/anonymous classes are not supported!"
+    }
+    // Top-level: package/of/class/MyClass
+    // Nested A:  package/of/class/MyClass.NestedClass
+    val simpleName = kotlinMetadataName.substringAfterLast(
+      '/', // Drop the package name, e.g. "package/of/class/"
+      '.', // Drop any enclosing classes, e.g. "MyClass."
+    )
+    val packageName = kotlinMetadataName.substringBeforeLast(
+      delimiter = "/",
+      missingDelimiterValue = "",
+    )
+    val simpleNames = kotlinMetadataName.removeSuffix(simpleName)
+      .removeSuffix(".") // Trailing "." if any
+      .removePrefix(packageName)
+      .removePrefix("/")
+      .let {
+        if (it.isNotEmpty()) {
+          it.split(".")
+        } else {
+          // Don't split, otherwise we end up with an empty string as the first element!
+          emptyList()
+        }
+      }
+      .plus(simpleName)
+
+    return ClassName(
+      packageName = packageName.replace("/", "."),
+      simpleNames = simpleNames,
+    )
+  }
+
+  fun Iterable<AnnotationSpec>.toTreeSet(): TreeSet<AnnotationSpec> {
+    return TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
+      addAll(this@toTreeSet)
+    }
+  }
+
+  private fun String.substringAfterLast(vararg delimiters: Char): String {
+    val index = lastIndexOfAny(delimiters)
+    return if (index == -1) this else substring(index + 1, length)
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
new file mode 100644
index 0000000..d4f7786
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.auto.common.Visibility
+import com.google.common.collect.LinkedHashMultimap
+import com.google.common.collect.SetMultimap
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JVM_NAME
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind.INTERFACE
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+private typealias ElementsModifier = javax.lang.model.element.Modifier
+
+/**
+ * An [Elements]-based implementation of [ClassInspector].
+ */
+@KotlinPoetMetadataPreview
+public class ElementsClassInspector private constructor(
+  private val elements: Elements,
+  private val types: Types,
+) : ClassInspector {
+  private val typeElementCache = ConcurrentHashMap<ClassName, Optional<TypeElement>>()
+  private val methodCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<ExecutableElement>>()
+  private val variableElementCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<VariableElement>>()
+  private val jvmNameType = elements.getTypeElement(JVM_NAME.canonicalName)
+  private val jvmNameName = ElementFilter.methodsIn(jvmNameType.enclosedElements)
+    .first { it.simpleName.toString() == "name" }
+
+  private fun lookupTypeElement(className: ClassName): TypeElement? {
+    return typeElementCache.getOrPut(className) {
+      elements.getTypeElement(className.canonicalName).toOptional()
+    }.nullableValue
+  }
+
+  override val supportsNonRuntimeRetainedAnnotations: Boolean = true
+
+  override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+    val typeElement = lookupTypeElement(className)
+      ?: error("No type element found for: $className.")
+
+    val metadata = typeElement.getAnnotation(Metadata::class.java)
+    return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+      is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+      is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+      else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+    }
+  }
+
+  override fun isInterface(className: ClassName): Boolean {
+    if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+      return true
+    }
+    return lookupTypeElement(className)?.kind == INTERFACE
+  }
+
+  private fun TypeElement.lookupField(fieldSignature: JvmFieldSignature): VariableElement? {
+    val signatureString = fieldSignature.asString()
+    return variableElementCache.getOrPut(this to signatureString) {
+      ElementFilter.fieldsIn(enclosedElements)
+        .find { signatureString == it.jvmFieldSignature(types) }.toOptional()
+    }.nullableValue
+  }
+
+  private fun lookupMethod(
+    className: ClassName,
+    methodSignature: JvmMethodSignature,
+    elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+  ): ExecutableElement? {
+    return lookupTypeElement(className)?.lookupMethod(methodSignature, elementFilter)
+  }
+
+  private fun TypeElement.lookupMethod(
+    methodSignature: JvmMethodSignature,
+    elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+  ): ExecutableElement? {
+    val signatureString = methodSignature.asString()
+    return methodCache.getOrPut(this to signatureString) {
+      elementFilter(enclosedElements)
+        .find { signatureString == it.jvmMethodSignature(types) }.toOptional()
+    }.nullableValue
+  }
+
+  private fun VariableElement.jvmModifiers(isJvmField: Boolean): Set<JvmFieldModifier> {
+    return modifiers.mapNotNullTo(mutableSetOf()) {
+      when {
+        it == ElementsModifier.TRANSIENT -> TRANSIENT
+        it == ElementsModifier.VOLATILE -> VOLATILE
+        !isJvmField && it == ElementsModifier.STATIC -> JvmFieldModifier.STATIC
+        else -> null
+      }
+    }
+  }
+
+  private fun VariableElement.annotationSpecs(): List<AnnotationSpec> {
+    @Suppress("DEPRECATION")
+    return filterOutNullabilityAnnotations(
+      annotationMirrors.map { AnnotationSpec.get(it) },
+    )
+  }
+
+  private fun ExecutableElement.jvmModifiers(): Set<JvmMethodModifier> {
+    return modifiers.mapNotNullTo(mutableSetOf()) {
+      when (it) {
+        ElementsModifier.SYNCHRONIZED -> SYNCHRONIZED
+        ElementsModifier.STATIC -> STATIC
+        ElementsModifier.DEFAULT -> DEFAULT
+        else -> null
+      }
+    }
+  }
+
+  private fun ExecutableElement.annotationSpecs(): List<AnnotationSpec> {
+    @Suppress("DEPRECATION")
+    return filterOutNullabilityAnnotations(
+      annotationMirrors.map { AnnotationSpec.get(it) },
+    )
+  }
+
+  private fun ExecutableElement.exceptionTypeNames(): List<TypeName> {
+    @Suppress("DEPRECATION")
+    return thrownTypes.map { it.asTypeName() }
+  }
+
+  override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+    val enumType = lookupTypeElement(enumClassName)
+      ?: error("No type element found for: $enumClassName.")
+    val enumTypeAsType = enumType.asType()
+    val member = typeElementCache.getOrPut(enumClassName.nestedClass(memberName)) {
+      ElementFilter.typesIn(enumType.enclosedElements)
+        .asSequence()
+        .filter { types.isSubtype(enumTypeAsType, it.superclass) }
+        .find { it.simpleName.contentEquals(memberName) }.toOptional()
+    }.nullableValue
+    val declarationContainer = member?.getAnnotation(Metadata::class.java)?.toKmClass()
+
+    val entry = ElementFilter.fieldsIn(enumType.enclosedElements)
+      .find { it.simpleName.contentEquals(memberName) }
+      ?: error("Could not find the enum entry for: $enumClassName")
+
+    return EnumEntryData(
+      declarationContainer = declarationContainer,
+      annotations = entry.annotationSpecs(),
+    )
+  }
+
+  private fun VariableElement.constantValue(): CodeBlock? {
+    return constantValue?.let(ClassInspectorUtil::codeLiteralOf)
+  }
+
+  override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+    return lookupMethod(className, methodSignature, ElementFilter::methodsIn) != null
+  }
+
+  /**
+   * Detects whether [this] given method is overridden in [type].
+   *
+   * Adapted and simplified from AutoCommon's private
+   * [MoreElements.getLocalAndInheritedMethods] methods implementations for detecting
+   * overrides.
+   */
+  private fun ExecutableElement.isOverriddenIn(type: TypeElement): Boolean {
+    val methodMap = LinkedHashMultimap.create<String, ExecutableElement>()
+    type.getAllMethods(MoreElements.getPackage(type), methodMap)
+    // Find methods that are overridden using `Elements.overrides`. We reduce the performance
+    // impact by:
+    //   (a) grouping methods by name, since a method cannot override another method with a
+    //       different name. Since we know the target name, we just inspect the methods with
+    //       that name.
+    //   (b) making sure that methods in ancestor types precede those in descendant types,
+    //       which means we only have to check a method against the ones that follow it in
+    //       that order. Below, this means we just need to find the index of our target method
+    //       and compare against only preceding ones.
+    val methodList = methodMap.asMap()[simpleName.toString()]?.toList()
+      ?: return false
+    val signature = jvmMethodSignature(types)
+    return methodList.asSequence()
+      .filter { it.jvmMethodSignature(types) == signature }
+      .take(1)
+      .any { elements.overrides(this, it, type) }
+  }
+
+  /**
+   * Add to [methodsAccumulator] the instance methods from [this] that are visible to code in
+   * the package [pkg]. This means all the instance methods from [this] itself and all
+   * instance methods it inherits from its ancestors, except private methods and
+   * package-private methods in other packages. This method does not take overriding into
+   * account, so it will add both an ancestor method and a descendant method that overrides
+   * it. [methodsAccumulator] is a multimap from a method name to all of the methods with
+   * that name, including methods that override or overload one another. Within those
+   * methods, those in ancestor types always precede those in descendant types.
+   *
+   * Adapted from AutoCommon's private [MoreElements.getLocalAndInheritedMethods] methods'
+   * implementations, before overridden methods are stripped.
+   */
+  private fun TypeElement.getAllMethods(
+    pkg: PackageElement,
+    methodsAccumulator: SetMultimap<String, ExecutableElement>,
+  ) {
+    for (superInterface in interfaces) {
+      MoreTypes.asTypeElement(superInterface).getAllMethods(pkg, methodsAccumulator)
+    }
+    if (superclass.kind != TypeKind.NONE) {
+      // Visit the superclass after superinterfaces so we will always see the implementation of a
+      // method after any interfaces that declared it.
+      MoreTypes.asTypeElement(superclass).getAllMethods(pkg, methodsAccumulator)
+    }
+    for (method in ElementFilter.methodsIn(enclosedElements)) {
+      if (ElementsModifier.STATIC !in method.modifiers &&
+        ElementsModifier.FINAL !in method.modifiers &&
+        ElementsModifier.PRIVATE !in method.modifiers &&
+        method.isVisibleFrom(pkg)
+      ) {
+        methodsAccumulator.put(method.simpleName.toString(), method)
+      }
+    }
+  }
+
+  private fun ExecutableElement.isVisibleFrom(pkg: PackageElement): Boolean {
+    // We use Visibility.ofElement rather than [MoreElements.effectiveVisibilityOfElement]
+    // because it doesn't really matter whether the containing class is visible. If you
+    // inherit a public method then you have a public method, regardless of whether you
+    // inherit it from a public class.
+    return when (Visibility.ofElement(this)) {
+      Visibility.PRIVATE -> false
+      Visibility.DEFAULT -> MoreElements.getPackage(this) == pkg
+      else -> true
+    }
+  }
+
+  override fun containerData(
+    declarationContainer: KmDeclarationContainer,
+    className: ClassName,
+    parentClassName: ClassName?,
+  ): ContainerData {
+    val typeElement: TypeElement = lookupTypeElement(className) ?: error("No class found for: $className.")
+    val isCompanionObject = when (declarationContainer) {
+      is KmClass -> {
+        declarationContainer.isCompanionObject
+      }
+      is KmPackage -> {
+        false
+      }
+      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+    }
+
+    // Should only be called if parentName has been null-checked
+    val classIfCompanion by lazy(NONE) {
+      if (isCompanionObject && parentClassName != null) {
+        lookupTypeElement(parentClassName)
+          ?: error("No class found for: $parentClassName.")
+      } else {
+        typeElement
+      }
+    }
+
+    val propertyData = declarationContainer.properties
+      .asSequence()
+      .filter { it.isDeclaration }
+      .filterNot { it.isSynthesized }
+      .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+        val isJvmField = ClassInspectorUtil.computeIsJvmField(
+          property = property,
+          classInspector = this,
+          isCompanionObject = isCompanionObject,
+          hasGetter = property.getterSignature != null,
+          hasSetter = property.setterSignature != null,
+          hasField = property.fieldSignature != null,
+        )
+
+        val fieldData = property.fieldSignature?.let fieldDataLet@{ fieldSignature ->
+          // Check the field in the parent first. For const/static/jvmField elements, these only
+          // exist in the parent and we want to check that if necessary to avoid looking up a
+          // non-existent field in the companion.
+          val parentModifiers = if (isCompanionObject && parentClassName != null) {
+            classIfCompanion.lookupField(fieldSignature)?.jvmModifiers(isJvmField).orEmpty()
+          } else {
+            emptySet()
+          }
+
+          val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+          // TODO we looked up field once, let's reuse it
+          val classForOriginalField = typeElement.takeUnless {
+            isCompanionObject &&
+              (property.isConst || isJvmField || isStatic)
+          } ?: classIfCompanion
+
+          val field = classForOriginalField.lookupField(fieldSignature)
+            ?: return@fieldDataLet FieldData.SYNTHETIC
+          val constant = if (property.hasConstant) {
+            val fieldWithConstant = classIfCompanion.takeIf { it != typeElement }?.let {
+              if (it.kind.isInterface) {
+                field
+              } else {
+                // const properties are relocated to the enclosing class
+                it.lookupField(fieldSignature)
+                  ?: return@fieldDataLet FieldData.SYNTHETIC
+              }
+            } ?: field
+            fieldWithConstant.constantValue()
+          } else {
+            null
+          }
+
+          val jvmModifiers = field.jvmModifiers(isJvmField) + parentModifiers
+
+          FieldData(
+            annotations = field.annotationSpecs(),
+            isSynthetic = false,
+            jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+              // JvmField companion objects don't need JvmStatic, it's implicit
+              isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+            },
+            constant = constant,
+          )
+        }
+
+        val getterData = property.getterSignature?.let { getterSignature ->
+          val method = classIfCompanion.lookupMethod(getterSignature, ElementFilter::methodsIn)
+          method?.methodData(
+            typeElement = typeElement,
+            hasAnnotations = property.getterFlags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+              ?.lookupMethod(getterSignature, ElementFilter::methodsIn)
+              ?: method,
+          )
+            ?: return@let MethodData.SYNTHETIC
+        }
+
+        val setterData = property.setterSignature?.let { setterSignature ->
+          val method = classIfCompanion.lookupMethod(setterSignature, ElementFilter::methodsIn)
+          method?.methodData(
+            typeElement = typeElement,
+            hasAnnotations = property.setterFlags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+              ?.lookupMethod(setterSignature, ElementFilter::methodsIn)
+              ?: method,
+            knownIsOverride = getterData?.isOverride,
+          )
+            ?: return@let MethodData.SYNTHETIC
+        }
+
+        val annotations = mutableListOf<AnnotationSpec>()
+        if (property.flags.hasAnnotations) {
+          property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+            val method = typeElement.lookupMethod(annotationsHolderSignature, ElementFilter::methodsIn)
+              ?: return@let MethodData.SYNTHETIC
+            annotations += method.annotationSpecs()
+              // Cover for https://github.com/square/kotlinpoet/issues/1046
+              .filterNot { it.typeName == JAVA_DEPRECATED }
+          }
+        }
+
+        // If a field is static in a companion object, remove the modifier and add the annotation
+        // directly on the top level. Otherwise this will generate `@field:JvmStatic`, which is
+        // not legal
+        var finalFieldData = fieldData
+        fieldData?.jvmModifiers?.let { modifiers ->
+          if (isCompanionObject && JvmFieldModifier.STATIC in modifiers) {
+            finalFieldData = fieldData.copy(
+              jvmModifiers = fieldData.jvmModifiers
+                .filterNotTo(LinkedHashSet()) { it == JvmFieldModifier.STATIC },
+            )
+            annotations += AnnotationSpec.builder(
+              JVM_STATIC,
+            ).build()
+          }
+        }
+
+        PropertyData(
+          annotations = annotations,
+          fieldData = finalFieldData,
+          getterData = getterData,
+          setterData = setterData,
+          isJvmField = isJvmField,
+        )
+      }
+
+    val methodData = declarationContainer.functions
+      .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+        val signature = kmFunction.signature
+        if (signature != null) {
+          val method = typeElement.lookupMethod(signature, ElementFilter::methodsIn)
+          method?.methodData(
+            typeElement = typeElement,
+            hasAnnotations = kmFunction.flags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+              ?.lookupMethod(signature, ElementFilter::methodsIn)
+              ?: method,
+          )
+            ?: return@associateWithTo MethodData.SYNTHETIC
+        } else {
+          MethodData.EMPTY
+        }
+      }
+
+    when (declarationContainer) {
+      is KmClass -> {
+        val constructorData = declarationContainer.constructors
+          .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+            if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+              //
+              // Annotations are interfaces in bytecode, but kotlin metadata will still report a
+              // constructor signature
+              //
+              // Inline classes have no constructors at runtime
+              //
+              return@associateWithTo ConstructorData.EMPTY
+            }
+            val signature = kmConstructor.signature
+            if (signature != null) {
+              val constructor = typeElement.lookupMethod(signature, ElementFilter::constructorsIn)
+                ?: return@associateWithTo ConstructorData.EMPTY
+              ConstructorData(
+                annotations = if (kmConstructor.flags.hasAnnotations) {
+                  constructor.annotationSpecs()
+                } else {
+                  emptyList()
+                },
+                parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+                isSynthetic = false,
+                jvmModifiers = constructor.jvmModifiers(),
+                exceptions = constructor.exceptionTypeNames(),
+              )
+            } else {
+              ConstructorData.EMPTY
+            }
+          }
+        return ClassData(
+          declarationContainer = declarationContainer,
+          className = className,
+          annotations = if (declarationContainer.flags.hasAnnotations) {
+            ClassInspectorUtil.createAnnotations {
+              @Suppress("DEPRECATION")
+              addAll(typeElement.annotationMirrors.map { AnnotationSpec.get(it) })
+            }
+          } else {
+            emptyList()
+          },
+          properties = propertyData,
+          constructors = constructorData,
+          methods = methodData,
+        )
+      }
+      is KmPackage -> {
+        // There's no flag for checking if there are annotations, so we just eagerly check in this
+        // case. All annotations on this class are file: site targets in source. This includes
+        // @JvmName.
+        var jvmName: String? = null
+        val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+          addAll(
+            typeElement.annotationMirrors.map {
+              if (it.annotationType == jvmNameType) {
+                val nameValue = requireNotNull(it.elementValues[jvmNameName]) {
+                  "No name property found on $it"
+                }
+                jvmName = nameValue.value as String
+              }
+              @Suppress("DEPRECATION")
+              AnnotationSpec.get(it)
+            },
+          )
+        }
+        return FileData(
+          declarationContainer = declarationContainer,
+          annotations = fileAnnotations,
+          properties = propertyData,
+          methods = methodData,
+          className = className,
+          jvmName = jvmName,
+        )
+      }
+      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+    }
+  }
+
+  private fun List<VariableElement>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+    return withIndex().associate { (index, parameter) ->
+      index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+    }
+  }
+
+  private fun ExecutableElement.methodData(
+    typeElement: TypeElement,
+    hasAnnotations: Boolean,
+    jvmInformationMethod: ExecutableElement = this,
+    knownIsOverride: Boolean? = null,
+  ): MethodData {
+    return MethodData(
+      annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+      parameterAnnotations = parameters.indexedAnnotationSpecs(),
+      isSynthetic = false,
+      jvmModifiers = jvmInformationMethod.jvmModifiers(),
+      isOverride = knownIsOverride ?: isOverriddenIn(typeElement),
+      exceptions = exceptionTypeNames(),
+    )
+  }
+
+  public companion object {
+    /** @return an [Elements]-based implementation of [ClassInspector]. */
+    @JvmStatic
+    @KotlinPoetMetadataPreview
+    public fun create(elements: Elements, types: Types): ClassInspector {
+      return ElementsClassInspector(elements, types)
+    }
+
+    private val JVM_STATIC = JvmStatic::class.asClassName()
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
new file mode 100644
index 0000000..235e5e8
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.classinspectors
+
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.QualifiedNameable
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor6
+import javax.lang.model.util.Types
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/*
+ * Adapted from
+ * - https://github.com/Takhion/kotlin-metadata/blob/e6de126575ad6ca10b093129b7c30d000c9b0c37/lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmDescriptorUtils.kt
+ * - https://github.com/Takhion/kotlin-metadata/pull/13
+ */
+
+/**
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ *
+ * @return the name of this [Element] in its "internal form".
+ */
+internal val Element.internalName: String
+  get() = when (this) {
+    is TypeElement -> {
+      when (nestingKind) {
+        NestingKind.TOP_LEVEL ->
+          qualifiedName.toString().replace('.', '/')
+        NestingKind.MEMBER ->
+          enclosingElement.internalName + "$" + simpleName
+        NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+          error("Unsupported nesting $nestingKind")
+        null ->
+          error("Unsupported, nestingKind == null")
+      }
+    }
+    is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+    else -> simpleName.toString()
+  }
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+@Suppress("unused")
+internal val NoType.descriptor: String
+  get() = "V"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val DeclaredType.descriptor: String
+  get() = "L" + asElement().internalName + ";"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val PrimitiveType.descriptor: String
+  get() = when (this.kind) {
+    BYTE -> "B"
+    CHAR -> "C"
+    DOUBLE -> "D"
+    FLOAT -> "F"
+    INT -> "I"
+    LONG -> "J"
+    SHORT -> "S"
+    BOOLEAN -> "Z"
+    else -> error("Unknown primitive type $this")
+  }
+
+/**
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeMirror.descriptor(types: Types): String =
+  accept(JvmDescriptorTypeVisitor, types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun WildcardType.descriptor(types: Types): String =
+  types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeVariable.descriptor(types: Types): String =
+  types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ArrayType.descriptor(types: Types): String =
+  "[" + componentType.descriptor(types)
+
+/**
+ * @return the "method descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ExecutableType.descriptor(types: Types): String {
+  val parameterDescriptors = parameterTypes.joinToString(separator = "") { it.descriptor(types) }
+  val returnDescriptor = returnType.descriptor(types)
+  return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun ExecutableElement.jvmMethodSignature(types: Types): String {
+  return "$simpleName${asType().descriptor(types)}"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+ *
+ * Useful for comparing with [JvmFieldSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun VariableElement.jvmFieldSignature(types: Types): String {
+  return "$simpleName:${asType().descriptor(types)}"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * - a "field descriptor", for example: `Ljava/lang/Object;`
+ * - a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor6<String, Types>() {
+  override fun visitNoType(t: NoType, types: Types): String = t.descriptor
+  override fun visitDeclared(t: DeclaredType, types: Types): String = t.descriptor
+  override fun visitPrimitive(t: PrimitiveType, types: Types): String = t.descriptor
+
+  override fun visitArray(t: ArrayType, types: Types): String = t.descriptor(types)
+  override fun visitWildcard(t: WildcardType, types: Types): String = t.descriptor(types)
+  override fun visitExecutable(t: ExecutableType, types: Types): String = t.descriptor(types)
+  override fun visitTypeVariable(t: TypeVariable, types: Types): String = t.descriptor(types)
+
+  override fun visitNull(t: NullType, types: Types): String = visitUnknown(
+    t,
+    types,
+  )
+  override fun visitError(t: ErrorType, types: Types): String = visitUnknown(
+    t,
+    types,
+  )
+
+  override fun visitUnknown(t: TypeMirror, types: Types): String = error("Unsupported type $t")
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
new file mode 100644
index 0000000..41935e3
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.classinspectors
+
+/**
+ * Simple `Optional` implementation for use in collections that don't allow `null` values.
+ */
+internal data class Optional<out T : Any>(val nullableValue: T?)
+
+internal fun <T : Any> T?.toOptional(): Optional<T> = Optional(
+  this,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
new file mode 100644
index 0000000..fc31ba0
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.lang.reflect.Parameter
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+@KotlinPoetMetadataPreview
+public class ReflectiveClassInspector private constructor(
+  private val classLoader: ClassLoader?,
+) : ClassInspector {
+
+  private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>()
+  private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>()
+  private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>()
+  private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>()
+  private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>()
+
+  private fun lookupClass(className: ClassName): Class<*>? {
+    return classCache.getOrPut(className) {
+      try {
+        if (classLoader == null) {
+          Class.forName(className.reflectionName())
+        } else {
+          Class.forName(className.reflectionName(), true, classLoader)
+        }
+      } catch (e: ClassNotFoundException) {
+        null
+      }.toOptional()
+    }.nullableValue
+  }
+
+  override val supportsNonRuntimeRetainedAnnotations: Boolean = false
+
+  override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+    val clazz = lookupClass(className)
+      ?: error("No type element found for: $className.")
+
+    val metadata = clazz.getAnnotation(Metadata::class.java)
+    return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+      is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+      is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+      else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+    }
+  }
+
+  override fun isInterface(className: ClassName): Boolean {
+    if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+      return true
+    }
+    return lookupClass(className)?.isInterface ?: false
+  }
+
+  private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? {
+    return try {
+      val signatureString = fieldSignature.asString()
+      fieldCache.getOrPut(this to signatureString) {
+        declaredFields
+          .asSequence()
+          .onEach { it.isAccessible = true }
+          .find { signatureString == it.jvmFieldSignature }.toOptional()
+      }.nullableValue
+    } catch (e: ClassNotFoundException) {
+      null
+    }
+  }
+
+  private fun Class<*>.lookupMethod(
+    methodSignature: JvmMethodSignature,
+  ): Method? {
+    val signatureString = methodSignature.asString()
+    return methodCache.getOrPut(this to signatureString) {
+      declaredMethods
+        .asSequence()
+        .onEach { it.isAccessible = true }
+        .find { signatureString == it.jvmMethodSignature }.toOptional()
+    }.nullableValue
+  }
+
+  private fun Class<*>.lookupConstructor(
+    constructorSignature: JvmMethodSignature,
+  ): Constructor<*>? {
+    val signatureString = constructorSignature.asString()
+    return constructorCache.getOrPut(this to signatureString) {
+      declaredConstructors
+        .asSequence()
+        .onEach { it.isAccessible = true }
+        .find { signatureString == it.jvmMethodSignature }.toOptional()
+    }.nullableValue
+  }
+
+  private fun Field.jvmModifiers(): Set<JvmFieldModifier> {
+    return mutableSetOf<JvmFieldModifier>().apply {
+      if (Modifier.isTransient(modifiers)) {
+        add(TRANSIENT)
+      }
+      if (Modifier.isVolatile(modifiers)) {
+        add(VOLATILE)
+      }
+      if (Modifier.isStatic(modifiers)) {
+        add(JvmFieldModifier.STATIC)
+      }
+    }
+  }
+
+  private fun Field.annotationSpecs(): List<AnnotationSpec> {
+    return filterOutNullabilityAnnotations(
+      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+    )
+  }
+
+  private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> {
+    return filterOutNullabilityAnnotations(
+      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) },
+    )
+  }
+
+  private fun Method.jvmModifiers(): Set<JvmMethodModifier> {
+    return methodJvmModifiers(modifiers, isDefault)
+  }
+
+  private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> {
+    return methodJvmModifiers(modifiers, false)
+  }
+
+  private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> {
+    val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>()
+    if (Modifier.isSynchronized(modifiers)) {
+      jvmMethodModifiers += SYNCHRONIZED
+    }
+    if (Modifier.isStatic(modifiers)) {
+      jvmMethodModifiers += STATIC
+    }
+    if (isDefault) {
+      jvmMethodModifiers += DEFAULT
+    }
+    return jvmMethodModifiers
+  }
+
+  private fun Method.annotationSpecs(): List<AnnotationSpec> {
+    return filterOutNullabilityAnnotations(
+      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+    )
+  }
+
+  private fun Parameter.annotationSpecs(): List<AnnotationSpec> {
+    return filterOutNullabilityAnnotations(
+      declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) },
+    )
+  }
+
+  private fun Method.exceptionTypeNames(): List<TypeName> {
+    return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+  }
+
+  private fun Constructor<*>.exceptionTypeNames(): List<TypeName> {
+    return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+  }
+
+  override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+    val clazz = lookupClass(enumClassName)
+      ?: error("No class found for: $enumClassName.")
+    check(clazz.isEnum) {
+      "Class must be an enum but isn't: $clazz"
+    }
+    val enumEntry = enumCache.getOrPut(clazz to memberName) {
+      clazz.enumConstants
+        .asSequence()
+        .map { it as Enum<*> }
+        .find { it.name == memberName }
+        .toOptional()
+    }.nullableValue
+    checkNotNull(enumEntry) {
+      "Could not find $memberName on $enumClassName"
+    }
+    return EnumEntryData(
+      declarationContainer = if (enumEntry.javaClass == clazz) {
+        // For simple enums with no class bodies, the entry class will be the same as the original
+        // class.
+        null
+      } else {
+        enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass()
+      },
+      annotations = clazz.getField(enumEntry.name).annotationSpecs(),
+    )
+  }
+
+  private fun Field.constantValue(): CodeBlock? {
+    if (!Modifier.isStatic(modifiers)) {
+      return null
+    }
+    return get(null) // Constant means we can do a static get on it.
+      .let(ClassInspectorUtil::codeLiteralOf)
+  }
+
+  private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean {
+    val signatureString = asString()
+    val classPackage = clazz.`package`.name
+    val interfaceMethods = clazz.interfaces.asSequence()
+      .flatMap { it.methods.asSequence() }
+    val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence()
+    return interfaceMethods.plus(superClassMethods)
+      .filterNot { Modifier.isFinal(it.modifiers) }
+      .filterNot { Modifier.isStatic(it.modifiers) }
+      .filterNot { Modifier.isPrivate(it.modifiers) }
+      .filter {
+        Modifier.isPublic(it.modifiers) ||
+          Modifier.isProtected(it.modifiers) ||
+          // Package private
+          it.declaringClass.`package`.name == classPackage
+      }
+      .map { it.jvmMethodSignature }
+      .any { it == signatureString }
+  }
+
+  override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+    return lookupClass(className)?.lookupMethod(methodSignature) != null
+  }
+
+  override fun containerData(
+    declarationContainer: KmDeclarationContainer,
+    className: ClassName,
+    parentClassName: ClassName?,
+  ): ContainerData {
+    val targetClass = lookupClass(className) ?: error("No class found for: $className.")
+    val isCompanionObject: Boolean = when (declarationContainer) {
+      is KmClass -> {
+        declarationContainer.isCompanionObject
+      }
+      is KmPackage -> {
+        false
+      }
+      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+    }
+
+    // Should only be called if parentName has been null-checked
+    val classIfCompanion by lazy(NONE) {
+      if (isCompanionObject && parentClassName != null) {
+        lookupClass(parentClassName)
+          ?: error("No class found for: $parentClassName.")
+      } else {
+        targetClass
+      }
+    }
+
+    val propertyData = declarationContainer.properties
+      .asSequence()
+      .filter { it.isDeclaration }
+      .filterNot { it.isSynthesized }
+      .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+        val isJvmField = ClassInspectorUtil.computeIsJvmField(
+          property = property,
+          classInspector = this,
+          isCompanionObject = isCompanionObject,
+          hasGetter = property.getterSignature != null,
+          hasSetter = property.setterSignature != null,
+          hasField = property.fieldSignature != null,
+        )
+
+        val fieldData = property.fieldSignature?.let { fieldSignature ->
+          // Check the field in the parent first. For const/static/jvmField elements, these only
+          // exist in the parent and we want to check that if necessary to avoid looking up a
+          // non-existent field in the companion.
+          val parentModifiers = if (isCompanionObject && parentClassName != null) {
+            classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty()
+          } else {
+            emptySet()
+          }
+
+          val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+          // TODO we looked up field once, let's reuse it
+          val classForOriginalField = targetClass.takeUnless {
+            isCompanionObject &&
+              (property.isConst || isJvmField || isStatic)
+          } ?: classIfCompanion
+
+          val field = classForOriginalField.lookupField(fieldSignature)
+            ?: error("No field $fieldSignature found in $classForOriginalField.")
+          val constant = if (property.hasConstant) {
+            val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let {
+              if (it.isInterface) {
+                field
+              } else {
+                // const properties are relocated to the enclosing class
+                it.lookupField(fieldSignature)
+                  ?: error("No field $fieldSignature found in $it.")
+              }
+            } ?: field
+            fieldWithConstant.constantValue()
+          } else {
+            null
+          }
+
+          val jvmModifiers = field.jvmModifiers() + parentModifiers
+
+          // For static, const, or JvmField fields in a companion object, the companion
+          // object's field is marked as synthetic to hide it from Java, but in this case
+          // it's a false positive for this check in kotlin.
+          val isSynthetic = field.isSynthetic &&
+            !(
+              isCompanionObject &&
+                (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers)
+              )
+
+          FieldData(
+            annotations = field.annotationSpecs(),
+            isSynthetic = isSynthetic,
+            jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+              // JvmField companion objects don't need JvmStatic, it's implicit
+              isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+            },
+            constant = constant,
+          )
+        }
+
+        val getterData = property.getterSignature?.let { getterSignature ->
+          val method = classIfCompanion.lookupMethod(getterSignature)
+          method?.methodData(
+            clazz = targetClass,
+            signature = getterSignature,
+            hasAnnotations = property.getterFlags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+              ?.lookupMethod(getterSignature) ?: method,
+          )
+            ?: error("No getter method $getterSignature found in $classIfCompanion.")
+        }
+
+        val setterData = property.setterSignature?.let { setterSignature ->
+          val method = classIfCompanion.lookupMethod(setterSignature)
+          method?.methodData(
+            clazz = targetClass,
+            signature = setterSignature,
+            hasAnnotations = property.setterFlags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+              ?.lookupMethod(setterSignature) ?: method,
+            knownIsOverride = getterData?.isOverride,
+          )
+            ?: error("No setter method $setterSignature found in $classIfCompanion.")
+        }
+
+        val annotations = mutableListOf<AnnotationSpec>()
+        if (property.flags.hasAnnotations) {
+          property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+            targetClass.lookupMethod(annotationsHolderSignature)?.let { method ->
+              annotations += method.annotationSpecs()
+                // Cover for https://github.com/square/kotlinpoet/issues/1046
+                .filterNot { it.typeName == JAVA_DEPRECATED }
+            }
+          }
+        }
+
+        PropertyData(
+          annotations = annotations,
+          fieldData = fieldData,
+          getterData = getterData,
+          setterData = setterData,
+          isJvmField = isJvmField,
+        )
+      }
+
+    val methodData = declarationContainer.functions
+      .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+        val signature = kmFunction.signature
+        if (signature != null) {
+          val method = targetClass.lookupMethod(signature)
+          method?.methodData(
+            clazz = targetClass,
+            signature = signature,
+            hasAnnotations = kmFunction.flags.hasAnnotations,
+            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature)
+              ?: method,
+          )
+            ?: error("No method $signature found in $targetClass.")
+        } else {
+          MethodData.EMPTY
+        }
+      }
+
+    when (declarationContainer) {
+      is KmClass -> {
+        val classAnnotations = if (declarationContainer.flags.hasAnnotations) {
+          ClassInspectorUtil.createAnnotations {
+            addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+          }
+        } else {
+          emptyList()
+        }
+        val constructorData = declarationContainer.constructors
+          .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+            if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+              //
+              // Annotations are interfaces in reflection, but kotlin metadata will still report a
+              // constructor signature
+              //
+              // Inline classes have no constructors at runtime
+              //
+              return@associateWithTo ConstructorData.EMPTY
+            }
+            val signature = kmConstructor.signature
+            if (signature != null) {
+              val constructor = targetClass.lookupConstructor(signature)
+                ?: error("No constructor $signature found in $targetClass.")
+              ConstructorData(
+                annotations = if (kmConstructor.flags.hasAnnotations) {
+                  constructor.annotationSpecs()
+                } else {
+                  emptyList()
+                },
+                parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+                isSynthetic = constructor.isSynthetic,
+                jvmModifiers = constructor.jvmModifiers(),
+                exceptions = constructor.exceptionTypeNames(),
+              )
+            } else {
+              ConstructorData.EMPTY
+            }
+          }
+        return ClassData(
+          declarationContainer = declarationContainer,
+          className = className,
+          annotations = classAnnotations,
+          properties = propertyData,
+          constructors = constructorData,
+          methods = methodData,
+        )
+      }
+      is KmPackage -> {
+        // There's no flag for checking if there are annotations, so we just eagerly check in this
+        // case. All annotations on this class are file: site targets in source. This does not
+        // include @JvmName since it does not have RUNTIME retention. In practice this doesn't
+        // really matter, but it does mean we can't know for certain if the file should be called
+        // FooKt.kt or Foo.kt.
+        val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+          addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+        }
+        return FileData(
+          declarationContainer = declarationContainer,
+          annotations = fileAnnotations,
+          properties = propertyData,
+          methods = methodData,
+          className = className,
+        )
+      }
+      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+    }
+  }
+
+  private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+    return withIndex().associate { (index, parameter) ->
+      index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+    }
+  }
+
+  private fun Method.methodData(
+    clazz: Class<*>,
+    signature: JvmMethodSignature,
+    hasAnnotations: Boolean,
+    jvmInformationMethod: Method = this,
+    knownIsOverride: Boolean? = null,
+  ): MethodData {
+    return MethodData(
+      annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+      parameterAnnotations = parameters.indexedAnnotationSpecs(),
+      isSynthetic = isSynthetic,
+      jvmModifiers = jvmInformationMethod.jvmModifiers(),
+      isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz),
+      exceptions = exceptionTypeNames(),
+    )
+  }
+
+  public companion object {
+    @JvmStatic
+    @KotlinPoetMetadataPreview
+    public fun create(classLoader: ClassLoader? = null): ClassInspector {
+      return ReflectiveClassInspector(classLoader)
+    }
+
+    private val Class<*>.descriptor: String
+      get() {
+        return when {
+          isPrimitive -> when (kotlin) {
+            Byte::class -> "B"
+            Char::class -> "C"
+            Double::class -> "D"
+            Float::class -> "F"
+            Int::class -> "I"
+            Long::class -> "J"
+            Short::class -> "S"
+            Boolean::class -> "Z"
+            Void::class -> "V"
+            else -> throw RuntimeException("Unrecognized primitive $this")
+          }
+          isArray -> name.replace('.', '/')
+          else -> "L$name;".replace('.', '/')
+        }
+      }
+
+    private val Method.descriptor: String
+      get() = parameterTypes.joinToString(
+        separator = "",
+        prefix = "(",
+        postfix = ")${returnType.descriptor}",
+      ) { it.descriptor }
+
+    /**
+     * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+     *
+     * Useful for comparing with [JvmMethodSignature].
+     *
+     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+     */
+    private val Method.jvmMethodSignature: String get() = "$name$descriptor"
+
+    private val Constructor<*>.descriptor: String
+      get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }
+
+    /**
+     * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`.
+     *
+     * Useful for comparing with [JvmMethodSignature].
+     *
+     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+     */
+    private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor"
+
+    /**
+     * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+     *
+     * Useful for comparing with [JvmFieldSignature].
+     *
+     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+     */
+    private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}"
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
new file mode 100644
index 0000000..14c019f
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/** A basic interface for looking up JVM information about a given Class. */
+@KotlinPoetMetadataPreview
+public interface ClassInspector {
+
+  /**
+   * Indicates if this [ClassInspector] supports [AnnotationRetention.RUNTIME]-retained annotations.
+   * This is used to indicate if manual inference of certain non-RUNTIME-retained annotations should
+   * be done, such as [JvmName].
+   */
+  public val supportsNonRuntimeRetainedAnnotations: Boolean
+
+  /**
+   * Creates a new [ContainerData] instance for a given [declarationContainer].
+   *
+   * @param declarationContainer the source [KmDeclarationContainer] to read from.
+   * @param className the [ClassName] of the target class to to read from.
+   * @param parentClassName the parent [ClassName] name if [declarationContainer] is nested, inner,
+   * or is a companion object.
+   */
+  public fun containerData(
+    declarationContainer: KmDeclarationContainer,
+    className: ClassName,
+    parentClassName: ClassName?,
+  ): ContainerData
+
+  /**
+   * Looks up other declaration containers, such as for nested members. Note that this class would
+   * always be Kotlin, so Metadata can be relied on for this.
+   *
+   * @param className The [ClassName] representation of the class.
+   * @return the read [KmDeclarationContainer] from its metadata. If no class or facade
+   *         file was found, this should throw an exception.
+   */
+  public fun declarationContainerFor(className: ClassName): KmDeclarationContainer
+
+  /**
+   * Looks up a class and returns whether or not it is an interface. Note that this class can be
+   * Java or Kotlin, so Metadata should not be relied on for this.
+   *
+   * @param className The [ClassName] representation of the class.
+   * @return whether or not it is an interface.
+   */
+  public fun isInterface(className: ClassName): Boolean
+
+  /**
+   * Looks up the enum entry on a given enum given its member name.
+   *
+   * @param enumClassName The [ClassName] representation of the enum class.
+   * @param memberName The simple member name.
+   * @return the [EnumEntryData]
+   */
+  public fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData
+
+  /**
+   * Looks up if a given [methodSignature] within [className] exists.
+   *
+   * @param className The [ClassName] representation of the class.
+   * @param methodSignature The method signature to check.
+   * @return whether or not the method exists.
+   */
+  public fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean
+}
+
+/**
+ * Creates a new [ContainerData] instance for a given [className].
+ *
+ * @param className the [ClassName] of the target class to to read from.
+ * @param parentClassName the parent [ClassName] name if [className] is nested, inner, or is a
+ *        companion object.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.containerData(
+  className: ClassName,
+  parentClassName: ClassName?,
+): ContainerData {
+  return containerData(declarationContainerFor(className), className, parentClassName)
+}
+
+/**
+ * Looks up other classes, such as for nested members. Note that this class would always be
+ * Kotlin, so Metadata can be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return the read [KmClass] from its metadata. If no class was found, this should throw
+ *         an exception.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.classFor(className: ClassName): KmClass {
+  val container = declarationContainerFor(className)
+  check(container is KmClass) {
+    "Container is not a class! Was ${container.javaClass.simpleName}"
+  }
+  return container
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
new file mode 100644
index 0000000..01c9839
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a constructor used for [ClassInspector]. Should only be
+ * associated with constructors of a [ClassData].
+ *
+ * @param annotations declared annotations on this constructor.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this constructor is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this constructor.
+ * @property exceptions list of exceptions thrown by this constructor.
+ */
+@KotlinPoetMetadataPreview
+public data class ConstructorData(
+  private val annotations: List<AnnotationSpec>,
+  val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+  val isSynthetic: Boolean,
+  val jvmModifiers: Set<JvmMethodModifier>,
+  val exceptions: List<TypeName>,
+) {
+
+  /**
+   * A collection of all annotations on this constructor, including any derived from [jvmModifiers],
+   * [isSynthetic], and [exceptions].
+   */
+  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+    addAll(annotations)
+    if (isSynthetic) {
+      add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+    }
+    addAll(jvmModifiers.mapNotNull { it.annotationSpec() })
+    exceptions.takeIf { it.isNotEmpty() }
+      ?.let {
+        add(ClassInspectorUtil.createThrowsSpec(it))
+      }
+  }
+
+  public companion object {
+    public val EMPTY: ConstructorData = ConstructorData(
+      annotations = emptyList(),
+      parameterAnnotations = emptyMap(),
+      isSynthetic = false,
+      jvmModifiers = emptySet(),
+      exceptions = emptyList(),
+    )
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
new file mode 100644
index 0000000..f1006d0
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+
+/**
+ * Represents relevant information on a declaration container used for [ClassInspector]. Can only
+ * ever be applied on a Kotlin type (i.e. is annotated with [Metadata]).
+ *
+ * @property declarationContainer the [KmDeclarationContainer] as parsed from the class's
+ *           [@Metadata][Metadata] annotation.
+ * @property annotations declared annotations on this class.
+ * @property properties the mapping of [declarationContainer]'s properties to parsed [PropertyData].
+ * @property methods the mapping of [declarationContainer]'s methods to parsed [MethodData].
+ */
+@KotlinPoetMetadataPreview
+public interface ContainerData {
+  public val declarationContainer: KmDeclarationContainer
+  public val annotations: Collection<AnnotationSpec>
+  public val properties: Map<KmProperty, PropertyData>
+  public val methods: Map<KmFunction, MethodData>
+}
+
+/**
+ * Represents relevant information on a Kotlin class used for [ClassInspector]. Can only ever be
+ * applied on a class and not file facades.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ *           [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the class.
+ * @property constructors the mapping of [declarationContainer]'s constructors to parsed
+ * [ConstructorData].
+ */
+@KotlinPoetMetadataPreview
+public data class ClassData(
+  override val declarationContainer: KmClass,
+  val className: ClassName,
+  override val annotations: Collection<AnnotationSpec>,
+  override val properties: Map<KmProperty, PropertyData>,
+  val constructors: Map<KmConstructor, ConstructorData>,
+  override val methods: Map<KmFunction, MethodData>,
+) : ContainerData
+
+/**
+ * Represents relevant information on a file facade used for [ClassInspector].
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ *           [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the underlying facade class in JVM.
+ * @property jvmName the `@JvmName` of the class or null if it does not have a custom name.
+ *           Default will try to infer from the [className].
+ */
+@KotlinPoetMetadataPreview
+public data class FileData(
+  override val declarationContainer: KmPackage,
+  override val annotations: Collection<AnnotationSpec>,
+  override val properties: Map<KmProperty, PropertyData>,
+  override val methods: Map<KmFunction, MethodData>,
+  val className: ClassName,
+  val jvmName: String? =
+    if (!className.simpleName.endsWith("Kt")) className.simpleName else null,
+) : ContainerData {
+
+  /**
+   * The file name of the container, defaults to [className]'s simple name + "Kt". If a [jvmName] is
+   * specified, it will always defer to that.
+   */
+  val fileName: String = jvmName ?: className.simpleName.removeSuffix("Kt")
+}
+
+/**
+ * Represents relevant information on a Kotlin enum entry.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the entry's
+ * [@Metadata][Metadata] annotation.
+ * @property annotations the annotations for the entry
+ */
+@KotlinPoetMetadataPreview
+public data class EnumEntryData(
+  val declarationContainer: KmClass?,
+  val annotations: Collection<AnnotationSpec>,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
new file mode 100644
index 0000000..a000dde
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a field used for [ClassInspector]. Should only be
+ * associated with a [PropertyData].
+ *
+ * @param annotations declared annotations on this field.
+ * @property isSynthetic indicates if this field is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this field.
+ * @property constant the constant value of this field, if available. Note that this is does not
+ *           strictly imply that the associated property is `const`.
+ */
+@KotlinPoetMetadataPreview
+public data class FieldData(
+  private val annotations: List<AnnotationSpec>,
+  val isSynthetic: Boolean,
+  val jvmModifiers: Set<JvmFieldModifier>,
+  val constant: CodeBlock?,
+) {
+
+  /**
+   * A collection of all annotations on this method, including any derived from [jvmModifiers]
+   * and [isSynthetic].
+   */
+  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations(
+    FIELD,
+  ) {
+    addAll(annotations)
+    if (isSynthetic) {
+      add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+    }
+    addAll(jvmModifiers.mapNotNull(JvmFieldModifier::annotationSpec))
+  }
+
+  public companion object {
+    public val SYNTHETIC: FieldData = FieldData(
+      annotations = emptyList(),
+      isSynthetic = true,
+      jvmModifiers = emptySet(),
+      constant = null,
+    )
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
new file mode 100644
index 0000000..dc2a55b
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmFieldModifier : JvmModifier {
+  STATIC {
+    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+      JvmStatic::class.asClassName(),
+    ).build()
+  },
+  TRANSIENT {
+    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+      Transient::class.asClassName(),
+    ).build()
+  },
+  VOLATILE {
+    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+      Volatile::class.asClassName(),
+    ).build()
+  },
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
new file mode 100644
index 0000000..5d63738
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations or implicit in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmMethodModifier : JvmModifier {
+  STATIC {
+    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+      JvmStatic::class.asClassName(),
+    ).build()
+  },
+  SYNCHRONIZED {
+    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+      Synchronized::class.asClassName(),
+    ).build()
+  },
+  DEFAULT,
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
new file mode 100644
index 0000000..f09e6ad
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/**
+ * Represents a JVM modifier that is represented as an annotation in Kotlin but as a modifier in
+ * bytecode. Examples include annotations such as [@JvmStatic][JvmStatic] or
+ * [@JvmSynthetic][JvmSynthetic].
+ *
+ * This API is considered read-only and should not be implemented outside of KotlinPoet.
+ */
+@KotlinPoetMetadataPreview
+public interface JvmModifier {
+  public fun annotationSpec(): AnnotationSpec? {
+    return null
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
new file mode 100644
index 0000000..cd12479
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.isNullable
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmClassifier.Class
+import kotlinx.metadata.KmClassifier.TypeAlias
+import kotlinx.metadata.KmClassifier.TypeParameter
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFlexibleTypeUpperBound
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmTypeProjection
+import kotlinx.metadata.KmVariance
+import kotlinx.metadata.KmVariance.IN
+import kotlinx.metadata.KmVariance.INVARIANT
+import kotlinx.metadata.KmVariance.OUT
+import kotlinx.metadata.jvm.annotations
+import kotlinx.metadata.jvm.signature
+
+/**
+ * `true` if this is an extension type (i.e. String.() -> Unit vs (String) -> Unit).
+ *
+ * See details: https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980/27
+ */
+public val KmType.isExtensionType: Boolean get() {
+  return annotations.any { it.className == "kotlin/ExtensionFunctionType" }
+}
+
+@KotlinPoetMetadataPreview
+internal val KmClass.primaryConstructor: KmConstructor?
+  get() = constructors.find { it.isPrimary }
+
+internal fun KmVariance.toKModifier(): KModifier? {
+  return when (this) {
+    IN -> KModifier.IN
+    OUT -> KModifier.OUT
+    INVARIANT -> null
+  }
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeProjection.toTypeName(
+  typeParamResolver: TypeParameterResolver,
+): TypeName {
+  val typename = type?.toTypeName(typeParamResolver) ?: STAR
+  return when (variance) {
+    IN -> WildcardTypeName.consumerOf(typename)
+    OUT -> WildcardTypeName.producerOf(typename)
+    INVARIANT -> typename
+    null -> STAR
+  }
+}
+
+/**
+ * Converts a given [KmType] into a KotlinPoet representation, attempting to give a correct
+ * "source" representation. This includes converting [functions][kotlin.Function] and `suspend`
+ * types to appropriate [lambda representations][LambdaTypeName].
+ */
+@KotlinPoetMetadataPreview
+internal fun KmType.toTypeName(
+  typeParamResolver: TypeParameterResolver,
+): TypeName {
+  val argumentList = arguments.map { it.toTypeName(typeParamResolver) }
+  val type: TypeName = when (val valClassifier = classifier) {
+    is TypeParameter -> {
+      typeParamResolver[valClassifier.id]
+    }
+    is KmClassifier.Class -> {
+      flexibleTypeUpperBound?.toTypeName(typeParamResolver)?.let { return it }
+      outerType?.toTypeName(typeParamResolver)?.let { return it }
+      var finalType: TypeName = ClassInspectorUtil.createClassName(valClassifier.name)
+      if (argumentList.isNotEmpty()) {
+        val finalTypeString = finalType.toString()
+        if (finalTypeString.startsWith("kotlin.Function")) {
+          // It's a lambda type!
+          finalType = if (finalTypeString == "kotlin.FunctionN") {
+            TODO("unclear how to express this one since it has arity")
+          } else {
+            val (parameters, returnType) = if (isSuspend) {
+              // Coroutines always adds an `Any?` return type, but we kind of just want the
+              // source representation, so we trick it here and ignore the last.
+              argumentList.dropLast(2).toTypedArray() to argumentList.dropLast(1).last().let {
+                // Coroutines makes these a `Continuation<T>` of the type, so we want the parameterized type
+                check(it is ParameterizedTypeName)
+                it.typeArguments[0]
+              }
+            } else {
+              argumentList.dropLast(1).toTypedArray() to argumentList.last()
+            }
+            val lambdaType = if (isExtensionType) {
+              // Extension function type! T.(). First parameter is actually the receiver.
+              LambdaTypeName.get(
+                receiver = parameters[0],
+                parameters = parameters.drop(1).toTypedArray(),
+                returnType = returnType,
+              )
+            } else {
+              LambdaTypeName.get(
+                receiver = null,
+                parameters = parameters,
+                returnType = returnType,
+              )
+            }
+            lambdaType.copy(suspending = isSuspend)
+          }
+        } else {
+          finalType = (finalType as ClassName).parameterizedBy(argumentList)
+        }
+      }
+      finalType
+    }
+    is TypeAlias -> {
+      ClassInspectorUtil.createClassName(valClassifier.name)
+    }
+  }
+
+  val annotations = ClassInspectorUtil.createAnnotations {
+    for (annotation in annotations) {
+      add(annotation.toAnnotationSpec())
+    }
+  }.toList()
+  val finalType = type.copy(nullable = isNullable, annotations = annotations)
+  return abbreviatedType?.let {
+    // This is actually an alias! The "abbreviated type" is the alias and how it's actually
+    // represented in source. So instead - we'll return the abbreviated type but store the "real"
+    // type in tags for reference.
+    val abbreviatedTypeName = it.toTypeName(typeParamResolver)
+    abbreviatedTypeName.copy(
+      tags = mapOf(TypeAliasTag::class to TypeAliasTag(finalType)),
+    )
+  } ?: finalType
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeParameter.toTypeVariableName(
+  typeParamResolver: TypeParameterResolver,
+): TypeVariableName {
+  val finalVariance = variance.toKModifier()
+  val typeVariableName = TypeVariableName(
+    name = name,
+    bounds = upperBounds.map { it.toTypeName(typeParamResolver) },
+    variance = finalVariance,
+  )
+  val annotations = ClassInspectorUtil.createAnnotations {
+    for (annotation in annotations) {
+      add(annotation.toAnnotationSpec())
+    }
+  }.toList()
+  return typeVariableName.copy(
+    reified = isReified,
+    tags = mapOf(KmTypeParameter::class to this),
+    annotations = annotations,
+  )
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFlexibleTypeUpperBound.toTypeName(
+  typeParamResolver: TypeParameterResolver,
+): TypeName {
+  // TODO tag typeFlexibilityId somehow?
+  return WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
+}
+
+internal interface TypeParameterResolver {
+  val parametersMap: Map<Int, TypeVariableName>
+  operator fun get(index: Int): TypeVariableName
+
+  companion object {
+    val EMPTY = object : TypeParameterResolver {
+      override val parametersMap: Map<Int, TypeVariableName> = emptyMap()
+
+      override fun get(index: Int): TypeVariableName = throw NoSuchElementException("No type parameters!")
+    }
+  }
+}
+
+@KotlinPoetMetadataPreview
+internal fun List<KmTypeParameter>.toTypeParameterResolver(
+  fallback: TypeParameterResolver? = null,
+): TypeParameterResolver {
+  val parametersMap = LinkedHashMap<Int, TypeVariableName>()
+  val typeParamResolver = { id: Int ->
+    parametersMap[id]
+      ?: fallback?.get(id)
+      ?: throw IllegalStateException("No type argument found for $id!")
+  }
+
+  val resolver = object : TypeParameterResolver {
+    override val parametersMap: Map<Int, TypeVariableName> = parametersMap
+
+    override operator fun get(index: Int): TypeVariableName = typeParamResolver(index)
+  }
+
+  // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
+  for (typeParam in this) {
+    // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
+    // replacement later that may add bounds referencing this.
+    parametersMap[typeParam.id] = TypeVariableName(typeParam.name)
+  }
+
+  for (typeParam in this) {
+    // Now replace it with the full version.
+    parametersMap[typeParam.id] = typeParam.toTypeVariableName(resolver)
+  }
+
+  return resolver
+}
+
+internal val KM_PROPERTY_COMPARATOR = Comparator<KmProperty> { o1, o2 ->
+  // No need to check fields, getters, etc as properties must have distinct names
+  o1.name.compareTo(o2.name)
+}
+
+internal val KM_FUNCTION_COMPARATOR = Comparator<KmFunction> { o1, o2 ->
+  var result = o1.name.compareTo(o2.name)
+  if (result != 0) return@Comparator result
+
+  val signature1 = o1.signature
+  val signature2 = o2.signature
+  if (signature1 != null && signature2 != null) {
+    result = signature1.asString().compareTo(signature2.asString())
+    if (result != 0) return@Comparator result
+  }
+
+  // Fallback - calculate signature
+  val manualSignature1 = o1.computeSignature()
+  val manualSignature2 = o2.computeSignature()
+  manualSignature1.compareTo(manualSignature2)
+}
+
+internal val KM_CONSTRUCTOR_COMPARATOR = Comparator<KmConstructor> { o1, o2 ->
+  val signature1 = o1.signature
+  val signature2 = o2.signature
+  if (signature1 != null && signature2 != null) {
+    val result = signature1.asString().compareTo(signature2.asString())
+    if (result != 0) return@Comparator result
+  }
+
+  // Fallback - calculate signature
+  val manualSignature1 = o1.computeSignature()
+  val manualSignature2 = o2.computeSignature()
+  manualSignature1.compareTo(manualSignature2)
+}
+
+// Computes a simple signature string good enough for hashing
+private fun KmFunction.computeSignature(): String {
+  return "$name(${valueParameters.joinToString(",") { it.type.simpleName }})${returnType.simpleName}"
+}
+
+private fun KmConstructor.computeSignature(): String {
+  return "$<init>(${valueParameters.joinToString(",") { it.type.simpleName }})"
+}
+
+private val KmType?.simpleName: String get() {
+  if (this == null) return "void"
+  return when (val c = classifier) {
+    is Class -> c.name
+    is TypeParameter -> "Object"
+    is TypeAlias -> c.name
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
new file mode 100644
index 0000000..6c0730a
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+@file:JvmName("KotlinPoetMetadataSpecs")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.FunSpec.Builder
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.CONST
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.FINAL
+import com.squareup.kotlinpoet.KModifier.INFIX
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.INNER
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.LATEINIT
+import com.squareup.kotlinpoet.KModifier.NOINLINE
+import com.squareup.kotlinpoet.KModifier.OPEN
+import com.squareup.kotlinpoet.KModifier.OPERATOR
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.SEALED
+import com.squareup.kotlinpoet.KModifier.SUSPEND
+import com.squareup.kotlinpoet.KModifier.TAILREC
+import com.squareup.kotlinpoet.KModifier.VALUE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.MemberName
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeAliasSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.UNIT
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_EXTERNAL
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_INLINE
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_NOT_DEFAULT
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createAnnotations
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.toTreeSet
+import com.squareup.kotlinpoet.metadata.declaresDefaultValue
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasGetter
+import com.squareup.kotlinpoet.metadata.hasSetter
+import com.squareup.kotlinpoet.metadata.isAbstract
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isClass
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isCrossInline
+import com.squareup.kotlinpoet.metadata.isData
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isDelegated
+import com.squareup.kotlinpoet.metadata.isDelegation
+import com.squareup.kotlinpoet.metadata.isEnum
+import com.squareup.kotlinpoet.metadata.isEnumEntry
+import com.squareup.kotlinpoet.metadata.isExpect
+import com.squareup.kotlinpoet.metadata.isExternal
+import com.squareup.kotlinpoet.metadata.isFinal
+import com.squareup.kotlinpoet.metadata.isFun
+import com.squareup.kotlinpoet.metadata.isInfix
+import com.squareup.kotlinpoet.metadata.isInline
+import com.squareup.kotlinpoet.metadata.isInner
+import com.squareup.kotlinpoet.metadata.isInterface
+import com.squareup.kotlinpoet.metadata.isInternal
+import com.squareup.kotlinpoet.metadata.isLateinit
+import com.squareup.kotlinpoet.metadata.isNoInline
+import com.squareup.kotlinpoet.metadata.isObject
+import com.squareup.kotlinpoet.metadata.isOpen
+import com.squareup.kotlinpoet.metadata.isOperator
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isPrivate
+import com.squareup.kotlinpoet.metadata.isProtected
+import com.squareup.kotlinpoet.metadata.isPublic
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSealed
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isTailRec
+import com.squareup.kotlinpoet.metadata.isVal
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.isVar
+import com.squareup.kotlinpoet.metadata.propertyAccessorFlags
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.toKmClass
+import com.squareup.kotlinpoet.tag
+import java.util.Locale
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeAlias
+import kotlinx.metadata.KmValueParameter
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.jvmInternalName
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toTypeSpec(
+  classInspector: ClassInspector? = null,
+): TypeSpec = java.toTypeSpec(classInspector)
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toTypeSpec(
+  classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [TypeSpec] ABI representation of this [TypeElement]. */
+@Suppress("DEPRECATION")
+@KotlinPoetMetadataPreview
+public fun TypeElement.toTypeSpec(
+  classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toFileSpec(
+  classInspector: ClassInspector? = null,
+): FileSpec = java.toFileSpec(classInspector)
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toFileSpec(
+  classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(`package`.name, toTypeSpec(classInspector))
+
+/** @return a [FileSpec] ABI representation of this [TypeElement]. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toFileSpec(
+  classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(
+  packageName = packageName,
+  typeSpec = toTypeSpec(classInspector),
+)
+
+/** @return a [TypeSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toTypeSpec(
+  classInspector: ClassInspector?,
+  className: ClassName = createClassName(name),
+): TypeSpec {
+  return toTypeSpec(classInspector, className, null)
+}
+
+/** @return a [FileSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toFileSpec(
+  classInspector: ClassInspector?,
+  className: ClassName = createClassName(name),
+): FileSpec {
+  return FileSpec.get(
+    packageName = className.packageName,
+    typeSpec = toTypeSpec(classInspector, className),
+  )
+}
+
+/** @return a [FileSpec] ABI representation of this [KmPackage]. */
+@KotlinPoetMetadataPreview
+public fun KmPackage.toFileSpec(
+  classInspector: ClassInspector?,
+  className: ClassName,
+): FileSpec {
+  val fileData = classInspector?.containerData(className, null)
+  check(fileData is FileData?) {
+    "Unexpected container data type: ${fileData?.javaClass}"
+  }
+  val fileName = fileData?.fileName ?: className.simpleName
+  return FileSpec.builder(className.packageName, fileName)
+    .apply {
+      fileData?.let { data ->
+        data.jvmName?.let { name ->
+          addAnnotation(
+            AnnotationSpec.builder(ClassInspectorUtil.JVM_NAME)
+              .addMember("name = %S", name)
+              .build(),
+          )
+        }
+        val fileAnnotations = createAnnotations(FILE) {
+          addAll(data.annotations.filterNot { it.typeName == METADATA })
+        }
+        for (fileAnnotation in fileAnnotations) {
+          addAnnotation(fileAnnotation)
+        }
+      }
+      for (function in functions) {
+        val methodData = fileData?.methods?.get(function)
+        addFunction(
+          function.toFunSpec(
+            classInspector = classInspector,
+            containerData = fileData,
+            methodData = methodData,
+            isInInterface = false,
+          ),
+        )
+      }
+      for (property in properties) {
+        val propertyData = fileData?.properties?.get(property)
+        addProperty(
+          property.toPropertySpec(
+            classInspector = classInspector,
+            containerData = fileData,
+            propertyData = propertyData,
+            isInInterface = false,
+          ),
+        )
+      }
+      for (alias in typeAliases) {
+        addTypeAlias(alias.toTypeAliasSpec())
+      }
+    }
+    .build()
+}
+
+private const val NOT_IMPLEMENTED = "throw·NotImplementedError(\"Stub!\")"
+
+@KotlinPoetMetadataPreview
+private fun KmClass.toTypeSpec(
+  classInspector: ClassInspector?,
+  className: ClassName,
+  parentClassName: ClassName?,
+): TypeSpec {
+  val classTypeParamsResolver = typeParameters.toTypeParameterResolver()
+  val jvmInternalName = name.jvmInternalName
+  val simpleName = className.simpleName
+  val classData = classInspector?.containerData(className, parentClassName)
+  check(classData is ClassData?) {
+    "Unexpected container data type: ${classData?.javaClass}"
+  }
+
+  val builder = when {
+    isAnnotation -> TypeSpec.annotationBuilder(simpleName)
+    isCompanionObject -> TypeSpec.companionObjectBuilder(companionObjectName(simpleName))
+    isEnum -> TypeSpec.enumBuilder(simpleName)
+    isExpect -> TypeSpec.expectClassBuilder(simpleName)
+    isObject -> TypeSpec.objectBuilder(simpleName)
+    isInterface -> {
+      if (classData?.declarationContainer?.isFun == true) {
+        TypeSpec.funInterfaceBuilder(simpleName)
+      } else {
+        TypeSpec.interfaceBuilder(simpleName)
+      }
+    }
+    isEnumEntry -> TypeSpec.anonymousClassBuilder()
+    else -> TypeSpec.classBuilder(simpleName)
+  }
+
+  classData?.annotations
+    ?.filterNot {
+      it.typeName == METADATA || it.typeName in JAVA_ANNOTATION_ANNOTATIONS
+    }
+    ?.let(builder::addAnnotations)
+
+  if (isEnum) {
+    enumEntries.forEach { entryName ->
+      val typeSpec = if (classInspector != null) {
+        val entry = classInspector.enumEntry(className, entryName)
+        entry.declarationContainer
+          ?.let { enumEntryClass ->
+            val entryClassName = className.nestedClass(entryName)
+            enumEntryClass.toTypeSpec(classInspector, entryClassName, parentClassName = className)
+          }
+          ?: TypeSpec.anonymousClassBuilder()
+            .addAnnotations(entry.annotations)
+            .build()
+      } else {
+        TypeSpec.anonymousClassBuilder()
+          .addKdoc(
+            "No ClassInspector was available during metadata parsing, so this entry may not be reflected accurately if it has a class body.",
+          )
+          .build()
+      }
+      builder.addEnumConstant(entryName, typeSpec)
+    }
+  }
+
+  if (!isEnumEntry) {
+    visibilityFrom(flags) { builder.addModifiers(it) }
+    builder.addModifiers(
+      *flags.modalities
+        .filterNot { it == FINAL } // Default
+        .filterNot { isInterface && it == ABSTRACT } // Abstract is a default on interfaces
+        .toTypedArray(),
+    )
+    if (isData) {
+      builder.addModifiers(DATA)
+    }
+    if (isExternal) {
+      builder.addModifiers(EXTERNAL)
+    }
+    if (isValue) {
+      builder.addModifiers(VALUE)
+    }
+    if (isInner) {
+      builder.addModifiers(INNER)
+    }
+    builder.addTypeVariables(typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) })
+    // If we have an inspector, we can check exactly which "supertype" is an interface vs
+    // class. Without a handler though, we have to best-effort guess. Usually, the flow is:
+    // - First element of a non-interface type is the superclass (can be `Any`)
+    // - First element of an interface type is the first superinterface
+    val superClassFilter = classInspector?.let { handler ->
+      { type: KmType ->
+        !handler.isInterface(createClassName((type.classifier as KmClassifier.Class).name))
+      }
+    } ?: { true }
+    val superClass = supertypes.asSequence()
+      .filter { it.classifier is KmClassifier.Class }
+      .find(superClassFilter)
+    if (superClass != null && !isEnum && !isInterface && !isAnnotation) {
+      superClass.toTypeName(classTypeParamsResolver).takeIf { it != ANY }
+        ?.let(builder::superclass)
+    }
+    builder.addSuperinterfaces(
+      supertypes.asSequence()
+        .filterNot { it == superClass }
+        .map { it.toTypeName(classTypeParamsResolver) }
+        .filterNot { it == ANY }
+        .asIterable(),
+    )
+    val primaryConstructorParams = mutableMapOf<String, ParameterSpec>()
+    if (isClass || isAnnotation || isEnum) {
+      primaryConstructor?.let {
+        it.toFunSpec(classTypeParamsResolver, classData?.constructors?.get(it) ?: return@let)
+          .also { spec ->
+            val finalSpec = if (isEnum && spec.annotations.isEmpty()) {
+              // Metadata specifies the constructor as private, but that's implicit so we can omit it
+              spec.toBuilder().apply { modifiers.remove(PRIVATE) }.build()
+            } else {
+              spec
+            }
+            builder.primaryConstructor(finalSpec)
+            primaryConstructorParams.putAll(spec.parameters.associateBy { it.name })
+          }
+      }
+      constructors.filter { !it.isPrimary }.takeIf { it.isNotEmpty() }?.let { secondaryConstructors ->
+        builder.addFunctions(
+          secondaryConstructors
+            .mapNotNull { kmConstructor ->
+              classData?.constructors?.get(kmConstructor)?.let { kmConstructor to it }
+            }
+            .map { (kmConstructor, constructorData) ->
+              kmConstructor.toFunSpec(classTypeParamsResolver, constructorData)
+            },
+        )
+      }
+    }
+    builder.addProperties(
+      properties
+        .asSequence()
+        .filter { it.isDeclaration }
+        .filterNot { it.isSynthesized }
+        .map { it to classData?.properties?.get(it) }
+        .map { (property, propertyData) ->
+          property.toPropertySpec(
+            typeParamResolver = classTypeParamsResolver,
+            isConstructorParam = property.name in primaryConstructorParams,
+            classInspector = classInspector,
+            containerData = classData,
+            propertyData = propertyData,
+          )
+        }
+        .asIterable(),
+    )
+    companionObject?.let { objectName ->
+      val companionType = if (classInspector != null) {
+        val companionClassName = className.nestedClass(objectName)
+        classInspector.classFor(companionClassName)
+          .toTypeSpec(classInspector, companionClassName, parentClassName = className)
+      } else {
+        TypeSpec.companionObjectBuilder(companionObjectName(objectName))
+          .addKdoc(
+            "No ClassInspector was available during metadata parsing, so this companion object's API/contents may not be reflected accurately.",
+          )
+          .build()
+      }
+      builder.addType(companionType)
+    }
+  }
+  builder.addFunctions(
+    functions
+      .asSequence()
+      .filter { it.isDeclaration }
+      .filterNot { it.isDelegation }
+      .filterNot { it.isSynthesized }
+      .map { it to classData?.methods?.get(it) }
+      .map { (func, methodData) ->
+        func.toFunSpec(classTypeParamsResolver, classInspector, classData, methodData)
+          .toBuilder()
+          .apply {
+            // For interface methods, remove any body and mark the methods as abstract
+            fun isKotlinDefaultInterfaceMethod(): Boolean {
+              classInspector?.let { handler ->
+                func.signature?.let { signature ->
+                  val suffix = signature.desc.removePrefix("(")
+                  return handler.methodExists(
+                    className.nestedClass("DefaultImpls"),
+                    signature.copy(
+                      desc = "(L$jvmInternalName;$suffix",
+                    ),
+                  )
+                }
+              }
+              return false
+            }
+            // For interface methods, remove any body and mark the methods as abstract
+            // IFF it doesn't have a default interface body.
+            if (isInterface &&
+              annotations.none { it.typeName == JVM_DEFAULT } &&
+              (methodData?.jvmModifiers?.contains(DEFAULT) == false) &&
+              !isKotlinDefaultInterfaceMethod()
+            ) {
+              addModifiers(ABSTRACT)
+              clearBody()
+            } else if (ABSTRACT in modifiers) {
+              // Remove bodies for abstract functions
+              clearBody()
+            }
+            if (methodData?.isSynthetic == true) {
+              addKdoc(
+                "Note: Since this is a synthetic function, some JVM information " +
+                  "(annotations, modifiers) may be missing.",
+              )
+            }
+          }
+          .build()
+      }
+      .asIterable(),
+  )
+
+  for (it in nestedClasses) {
+    val nestedClassName = className.nestedClass(it)
+    val nestedClass = classInspector?.classFor(nestedClassName)
+    val nestedType = if (nestedClass != null) {
+      if (nestedClass.isCompanionObject) {
+        // We handle these separately
+        continue
+      } else {
+        nestedClass.toTypeSpec(classInspector, nestedClassName, parentClassName = className)
+      }
+    } else {
+      TypeSpec.classBuilder(it)
+        .addKdoc(
+          "No ClassInspector was available during metadata parsing, so this nested class's API/contents may not be reflected accurately.",
+        )
+        .build()
+    }
+    builder.addType(nestedType)
+  }
+
+  return builder
+    .tag(this)
+    .build()
+}
+
+private fun companionObjectName(name: String): String? {
+  return if (name == "Companion") null else name
+}
+
+@KotlinPoetMetadataPreview
+private fun KmConstructor.toFunSpec(
+  typeParamResolver: TypeParameterResolver,
+  constructorData: ConstructorData?,
+): FunSpec {
+  return FunSpec.constructorBuilder()
+    .apply {
+      addAnnotations(constructorData?.allAnnotations.orEmpty())
+      visibilityFrom(flags) { addModifiers(it) }
+      addParameters(
+        this@toFunSpec.valueParameters.mapIndexed { index, param ->
+          param.toParameterSpec(
+            typeParamResolver,
+            constructorData?.takeIf { it != ConstructorData.EMPTY }
+              ?.parameterAnnotations
+              ?.get(index)
+              .orEmpty(),
+          )
+        },
+      )
+      if (!isPrimary) {
+        // TODO How do we know when to add callSuperConstructor()?
+      }
+    }
+    .tag(this)
+    .build()
+}
+
+@KotlinPoetMetadataPreview
+private val ContainerData.isInterface: Boolean get() {
+  return declarationContainer.let { container ->
+    container is KmClass && container.isInterface
+  }
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFunction.toFunSpec(
+  classTypeParamsResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+  classInspector: ClassInspector? = null,
+  containerData: ContainerData? = null,
+  methodData: MethodData? = null,
+  isInInterface: Boolean = containerData?.isInterface ?: false,
+): FunSpec {
+  val typeParamsResolver = typeParameters.toTypeParameterResolver(
+    fallback = classTypeParamsResolver,
+  )
+  val mutableAnnotations = mutableListOf<AnnotationSpec>()
+  if (classInspector != null && containerData != null) {
+    signature?.let { signature ->
+      if (!containerData.isInterface) {
+        // Infer if JvmName was used
+        // We skip interface types for this because they can't have @JvmName.
+        signature.jvmNameAnnotation(name)?.let { jvmNameAnnotation ->
+          mutableAnnotations += jvmNameAnnotation
+        }
+      }
+    }
+  }
+  val anyReified = typeParameters.any { it.isReified }
+  val isInFacade = containerData is FileData
+  val annotations = mutableAnnotations
+    .plus(methodData?.allAnnotations(containsReifiedTypeParameter = anyReified).orEmpty())
+    .filterNot { isInFacade && it.typeName == JVM_STATIC }
+    .toTreeSet()
+  return FunSpec.builder(name)
+    .apply {
+      addAnnotations(annotations)
+      visibilityFrom(flags) { addModifiers(it) }
+      val isOverride = methodData?.isOverride == true
+      addModifiers(
+        flags.modalities
+          .filterNot { it == FINAL && !isOverride } // Final is the default
+          .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+          .filterNot { it == OPEN && isInInterface }, // interface methods are implicitly open
+      )
+      if (valueParameters.isNotEmpty()) {
+        addParameters(
+          valueParameters.mapIndexed { index, param ->
+            param.toParameterSpec(
+              typeParamsResolver,
+              // This can be empty if the element is synthetic
+              methodData?.parameterAnnotations?.get(index).orEmpty(),
+            )
+          },
+        )
+      }
+      if (typeParameters.isNotEmpty()) {
+        addTypeVariables(typeParameters.map { it.toTypeVariableName(typeParamsResolver) })
+      }
+      if (methodData?.isOverride == true) {
+        addModifiers(KModifier.OVERRIDE)
+      }
+      if (isOperator) {
+        addModifiers(OPERATOR)
+      }
+      if (isInfix) {
+        addModifiers(INFIX)
+      }
+      if (isInline) {
+        addModifiers(INLINE)
+      }
+      if (isTailRec) {
+        addModifiers(TAILREC)
+      }
+      if (isExternal) {
+        addModifiers(EXTERNAL)
+      }
+      if (isExpect) {
+        addModifiers(EXPECT)
+      }
+      if (isSuspend) {
+        addModifiers(SUSPEND)
+      }
+      val returnTypeName = this@toFunSpec.returnType.toTypeName(typeParamsResolver)
+      if (returnTypeName != UNIT) {
+        returns(returnTypeName)
+        if (!flags.isAbstract) {
+          addStatement(NOT_IMPLEMENTED)
+        }
+      }
+      receiverParameterType?.toTypeName(typeParamsResolver)?.let { receiver(it) }
+    }
+    .tag(this)
+    .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmValueParameter.toParameterSpec(
+  typeParamResolver: TypeParameterResolver,
+  annotations: Collection<AnnotationSpec>,
+): ParameterSpec {
+  val paramType = varargElementType ?: type ?: throw IllegalStateException("No argument type!")
+  return ParameterSpec.builder(name, paramType.toTypeName(typeParamResolver))
+    .apply {
+      addAnnotations(annotations)
+      if (varargElementType != null) {
+        addModifiers(VARARG)
+      }
+      if (isCrossInline) {
+        addModifiers(CROSSINLINE)
+      }
+      if (isNoInline) {
+        addModifiers(NOINLINE)
+      }
+      if (declaresDefaultValue) {
+        defaultValue(NOT_IMPLEMENTED)
+      }
+    }
+    .tag(this)
+    .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmProperty.toPropertySpec(
+  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+  isConstructorParam: Boolean = false,
+  classInspector: ClassInspector? = null,
+  containerData: ContainerData? = null,
+  propertyData: PropertyData? = null,
+  isInInterface: Boolean = containerData?.isInterface ?: false,
+): PropertySpec {
+  val isOverride = propertyData?.isOverride ?: false
+  val returnTypeName = returnType.toTypeName(typeParamResolver)
+  val mutableAnnotations = mutableListOf<AnnotationSpec>()
+  if (containerData != null && propertyData != null) {
+    if (hasGetter) {
+      getterSignature?.let { getterSignature ->
+        if (!containerData.isInterface &&
+          !flags.isOpen &&
+          !flags.isAbstract
+        ) {
+          // Infer if JvmName was used
+          // We skip interface types or open/abstract properties because they can't have @JvmName.
+          // For annotation properties, kotlinc puts JvmName annotations by default in
+          // bytecode but they're implicit in source, so we expect the simple name for
+          // annotation types.
+          val expectedMetadataName = if (containerData is ClassData &&
+            containerData.declarationContainer.isAnnotation
+          ) {
+            name
+          } else {
+            "get${name.safeCapitalize(Locale.US)}"
+          }
+          getterSignature.jvmNameAnnotation(
+            metadataName = expectedMetadataName,
+            useSiteTarget = UseSiteTarget.GET,
+          )?.let { jvmNameAnnotation ->
+            mutableAnnotations += jvmNameAnnotation
+          }
+        }
+      }
+    }
+    if (hasSetter) {
+      setterSignature?.let { setterSignature ->
+        if (containerData is ClassData &&
+          !containerData.declarationContainer.isAnnotation &&
+          !containerData.declarationContainer.isInterface &&
+          classInspector?.supportsNonRuntimeRetainedAnnotations == false &&
+          !flags.isOpen &&
+          !flags.isAbstract
+        ) {
+          // Infer if JvmName was used
+          // We skip annotation types for this because they can't have vars.
+          // We skip interface types or open/abstract properties because they can't have @JvmName.
+          setterSignature.jvmNameAnnotation(
+            metadataName = "set${name.safeCapitalize(Locale.US)}",
+            useSiteTarget = UseSiteTarget.SET,
+          )?.let { jvmNameAnnotation ->
+            mutableAnnotations += jvmNameAnnotation
+          }
+        }
+      }
+    }
+  }
+  return PropertySpec.builder(name, returnTypeName)
+    .apply {
+      // If a property annotation doesn't have a custom site target and is used in a constructor
+      // we have to add the property: site target to it.
+
+      val isInFacade = containerData is FileData
+      val finalAnnotations = mutableAnnotations
+        .plus(propertyData?.allAnnotations.orEmpty())
+        .filterNot { (isConst || isInFacade) && it.typeName == JVM_STATIC }
+        .map {
+          if (isConstructorParam && it.useSiteTarget == null) {
+            // TODO Ideally don't do this if the annotation use site is only field?
+            //  e.g. JvmField. It's technically fine, but redundant on parameters as it's
+            //  automatically applied to the property for these annotation types.
+            //  This is another thing ClassInspector *could* tell us
+            it.toBuilder().useSiteTarget(UseSiteTarget.PROPERTY).build()
+          } else {
+            it
+          }
+        }
+        .toTreeSet()
+      addAnnotations(finalAnnotations)
+      visibilityFrom(flags) { addModifiers(it) }
+      addModifiers(
+        flags.modalities
+          .filterNot { it == FINAL && !isOverride } // Final is the default
+          .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+          .filterNot { it == OPEN && isInInterface } // Interface properties implicitly open
+          .filterNot { it == ABSTRACT && isInInterface }, // Interface properties implicitly abstract
+      )
+      if (isOverride) {
+        addModifiers(KModifier.OVERRIDE)
+      }
+      if (isConst) {
+        addModifiers(CONST)
+      }
+      if (isVar) {
+        mutable(true)
+      } else if (isVal) {
+        mutable(false)
+      }
+      if (isDelegated) {
+        // Placeholders for these are tricky
+        addKdoc("Note: delegation is ABI stub only and not guaranteed to match source code.")
+        if (isVal) {
+          delegate("%M { %L }", MemberName("kotlin", "lazy"), NOT_IMPLEMENTED) // Placeholder
+        } else {
+          if (returnTypeName.isNullable) {
+            delegate(
+              "%T.observable(null) { _, _, _ -> }",
+              ClassName("kotlin.properties", "Delegates"),
+            )
+          } else {
+            delegate("%T.notNull()", ClassName("kotlin.properties", "Delegates")) // Placeholder
+          }
+        }
+      }
+      if (isExpect) {
+        addModifiers(EXPECT)
+      }
+      if (isExternal) {
+        addModifiers(EXTERNAL)
+      }
+      if (isLateinit) {
+        addModifiers(LATEINIT)
+      }
+      if (isConstructorParam || (!isDelegated && !isLateinit)) {
+        val constant = propertyData?.fieldData?.constant
+        when {
+          constant != null -> initializer(constant)
+          isConstructorParam -> initializer(name)
+          returnTypeName.isNullable -> initializer("null")
+          flags.isAbstract || isInInterface -> {
+            // No-op, don't emit an initializer for abstract or interface properties
+          }
+          else -> initializer(NOT_IMPLEMENTED)
+        }
+      }
+      // Delegated properties have setters/getters defined for some reason, ignore here
+      // since the delegate handles it
+      // vals with initialized constants have a getter in bytecode but not a body in kotlin source
+      val modifierSet = modifiers.toSet()
+      if (hasGetter && !isDelegated && !flags.isAbstract) {
+        propertyAccessor(
+          modifierSet,
+          getterFlags,
+          FunSpec.getterBuilder().addStatement(NOT_IMPLEMENTED),
+          isOverride,
+        )?.let(::getter)
+      }
+      if (hasSetter && !isDelegated && !flags.isAbstract) {
+        propertyAccessor(modifierSet, setterFlags, FunSpec.setterBuilder(), isOverride)?.let(::setter)
+      }
+    }
+    .tag(this)
+    .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun propertyAccessor(
+  propertyModifiers: Set<KModifier>,
+  flags: Flags,
+  functionBuilder: Builder,
+  isOverride: Boolean,
+): FunSpec? {
+  val visibility = flags.visibility
+  if (visibility == PUBLIC || visibility !in propertyModifiers) {
+    // This is redundant and just a stub
+    // For annotations on this accessor, we declare them on the property with site target instead
+    return null
+  }
+  val modalities = flags.modalities
+    .filterNot { it == FINAL && !isOverride }
+    .filterNot { it == OPEN && isOverride }
+  val propertyAccessorFlags = flags.propertyAccessorFlags
+  return if (visibility != PUBLIC || modalities.isNotEmpty() || propertyAccessorFlags.isNotEmpty()) {
+    functionBuilder
+      .apply {
+        addModifiers(visibility)
+        addModifiers(modalities)
+        addModifiers(*propertyAccessorFlags.toKModifiersArray())
+      }
+      .build()
+  } else {
+    null
+  }
+}
+
+private fun Set<PropertyAccessorFlag>.toKModifiersArray(): Array<KModifier> {
+  return mapNotNull {
+    when (it) {
+      IS_EXTERNAL -> EXTERNAL
+      IS_INLINE -> INLINE
+      IS_NOT_DEFAULT -> null // Gracefully skip over these
+    }
+  }.toTypedArray()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmTypeAlias.toTypeAliasSpec(): TypeAliasSpec {
+  val typeParamResolver = typeParameters.toTypeParameterResolver()
+  return TypeAliasSpec.builder(name, underlyingType.toTypeName(typeParamResolver))
+    .apply {
+      visibilityFrom(flags) {
+        addModifiers(it)
+      }
+      if (flags.hasAnnotations) {
+        val annotationSpecs = this@toTypeAliasSpec.annotations
+          .map { it.toAnnotationSpec() }
+        addAnnotations(annotationSpecs)
+      }
+    }
+    .addTypeVariables(typeParamResolver.parametersMap.values)
+    .build()
+}
+
+private fun JvmMethodSignature.jvmNameAnnotation(
+  metadataName: String,
+  useSiteTarget: UseSiteTarget? = null,
+): AnnotationSpec? {
+  return if (name == metadataName) {
+    null
+  } else {
+    return AnnotationSpec.builder(JvmName::class)
+      .addMember("name = %S", name)
+      .useSiteTarget(useSiteTarget)
+      .build()
+  }
+}
+
+private val JAVA_ANNOTATION_ANNOTATIONS = setOf(
+  java.lang.annotation.Retention::class.asClassName(),
+  java.lang.annotation.Target::class.asClassName(),
+)
+
+@KotlinPoetMetadataPreview
+private val Flags.visibility: KModifier
+  get() = when {
+    isInternal -> INTERNAL
+    isPrivate -> PRIVATE
+    isProtected -> PROTECTED
+    isPublic -> PUBLIC
+    else -> {
+      // IS_PRIVATE_TO_THIS or IS_LOCAL, so just default to public
+      PUBLIC
+    }
+  }
+
+@KotlinPoetMetadataPreview
+private fun visibilityFrom(flags: Flags, body: (KModifier) -> Unit) {
+  val modifierVisibility = flags.visibility
+  if (modifierVisibility != PUBLIC) {
+    body(modifierVisibility)
+  }
+}
+
+private fun String.safeCapitalize(locale: Locale): String {
+  return replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
+}
+
+@KotlinPoetMetadataPreview
+private val Flags.modalities: Set<KModifier>
+  get() = setOf {
+    if (isFinal) {
+      add(FINAL)
+    }
+    if (isOpen) {
+      add(OPEN)
+    }
+    if (isAbstract) {
+      add(ABSTRACT)
+    }
+    if (isSealed) {
+      add(SEALED)
+    }
+  }
+
+private inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+  return mutableSetOf<E>().apply(body).toSet()
+}
+
+private val METADATA = Metadata::class.asClassName()
+
+@Suppress("DEPRECATION")
+private val JVM_DEFAULT = JvmDefault::class.asClassName()
+private val JVM_STATIC = JvmStatic::class.asClassName()
+
+@PublishedApi
+internal val Element.packageName: String
+  get() {
+    var element = this
+    while (element.kind != ElementKind.PACKAGE) {
+      element = element.enclosingElement
+    }
+    return (element as PackageElement).toString()
+  }
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
new file mode 100644
index 0000000..7319e9d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a method used for [ClassInspector]. Should only be
+ * associated with methods of a [ClassData] or [PropertyData].
+ *
+ * @param annotations declared annotations on this method.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this method is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this method.
+ * @property isOverride indicates if this method overrides one in a supertype.
+ * @property exceptions list of exceptions thrown by this method.
+ */
+@KotlinPoetMetadataPreview
+public data class MethodData(
+  private val annotations: List<AnnotationSpec>,
+  val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+  val isSynthetic: Boolean,
+  val jvmModifiers: Set<JvmMethodModifier>,
+  val isOverride: Boolean,
+  val exceptions: List<TypeName>,
+) {
+
+  /**
+   * A collection of all annotations on this method, including any derived from [jvmModifiers],
+   * [isSynthetic], and [exceptions].
+   *
+   * @param useSiteTarget an optional [UseSiteTarget] that all annotations on this method should
+   *        use.
+   * @param containsReifiedTypeParameter an optional boolean indicating if any type parameters on
+   *        this function are `reified`, which are implicitly synthetic.
+   */
+  public fun allAnnotations(
+    useSiteTarget: UseSiteTarget? = null,
+    containsReifiedTypeParameter: Boolean = false,
+  ): Collection<AnnotationSpec> {
+    return ClassInspectorUtil.createAnnotations(
+      useSiteTarget,
+    ) {
+      addAll(annotations)
+      if (isSynthetic && !containsReifiedTypeParameter) {
+        add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+      }
+      addAll(jvmModifiers.mapNotNull(JvmMethodModifier::annotationSpec))
+      exceptions.takeIf { it.isNotEmpty() }
+        ?.let {
+          add(ClassInspectorUtil.createThrowsSpec(it, useSiteTarget))
+        }
+    }
+  }
+
+  public companion object {
+    public val SYNTHETIC: MethodData = MethodData(
+      annotations = emptyList(),
+      parameterAnnotations = emptyMap(),
+      isSynthetic = true,
+      jvmModifiers = emptySet(),
+      isOverride = false,
+      exceptions = emptyList(),
+    )
+    public val EMPTY: MethodData = MethodData(
+      annotations = emptyList(),
+      parameterAnnotations = emptyMap(),
+      isSynthetic = false,
+      jvmModifiers = emptySet(),
+      isOverride = false,
+      exceptions = emptyList(),
+    )
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
new file mode 100644
index 0000000..9fc6d3e
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.GET
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a property used for [ClassInspector]. Should only be
+ * associated with properties of a [ClassData].
+ *
+ * @param annotations declared annotations on this property.
+ * @property fieldData associated [FieldData] with this property, if any.
+ * @property getterData associated getter (as [MethodData]) with this property, if any.
+ * @property setterData associated setter (as [MethodData]) with this property, if any.
+ * @property isJvmField indicates if this property should be treated as a jvm field.
+ */
+@KotlinPoetMetadataPreview
+public data class PropertyData(
+  private val annotations: List<AnnotationSpec>,
+  val fieldData: FieldData?,
+  val getterData: MethodData?,
+  val setterData: MethodData?,
+  val isJvmField: Boolean,
+) {
+  /** Indicates if this property overrides another from a supertype. */
+  val isOverride: Boolean = (getterData?.isOverride ?: false) || (setterData?.isOverride ?: false)
+
+  /**
+   * A collection of all annotations on this property including declared ones and any derived from
+   * [fieldData], [getterData], [setterData], and [isJvmField].
+   */
+  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+    // Don't add annotations that are already defined on the parent
+    val higherScopedAnnotations = annotations.associateBy { it.typeName }
+    val fieldAnnotations = fieldData?.allAnnotations.orEmpty()
+      .filterNot { it.typeName in higherScopedAnnotations }
+      .associateByTo(LinkedHashMap()) { it.typeName }
+    val getterAnnotations = getterData?.allAnnotations(GET).orEmpty()
+      .filterNot { it.typeName in higherScopedAnnotations }
+      .associateByTo(LinkedHashMap()) { it.typeName }
+
+    val finalTopAnnotations = annotations.toMutableList()
+
+    // If this is a val, and annotation is on both getter and field, we can move it to just the
+    // regular annotations
+    if (setterData == null && !isJvmField) {
+      val sharedAnnotations = getterAnnotations.keys.intersect(fieldAnnotations.keys)
+      for (sharedAnnotation in sharedAnnotations) {
+        // Add it to the top-level annotations without a site-target
+        finalTopAnnotations += getterAnnotations.getValue(sharedAnnotation).toBuilder()
+          .useSiteTarget(null)
+          .build()
+
+        // Remove from field and getter
+        fieldAnnotations.remove(sharedAnnotation)
+        getterAnnotations.remove(sharedAnnotation)
+      }
+    }
+
+    addAll(finalTopAnnotations)
+    addAll(fieldAnnotations.values)
+    addAll(getterAnnotations.values)
+    addAll(
+      setterData?.allAnnotations(SET).orEmpty()
+        .filterNot { it.typeName in higherScopedAnnotations },
+    )
+    if (isJvmField) {
+      add(ClassInspectorUtil.JVM_FIELD_SPEC)
+    }
+  }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
new file mode 100644
index 0000000..f6b4467
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.tag
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue
+import kotlinx.metadata.KmAnnotationArgument.BooleanValue
+import kotlinx.metadata.KmAnnotationArgument.ByteValue
+import kotlinx.metadata.KmAnnotationArgument.CharValue
+import kotlinx.metadata.KmAnnotationArgument.DoubleValue
+import kotlinx.metadata.KmAnnotationArgument.EnumValue
+import kotlinx.metadata.KmAnnotationArgument.FloatValue
+import kotlinx.metadata.KmAnnotationArgument.IntValue
+import kotlinx.metadata.KmAnnotationArgument.KClassValue
+import kotlinx.metadata.KmAnnotationArgument.LongValue
+import kotlinx.metadata.KmAnnotationArgument.ShortValue
+import kotlinx.metadata.KmAnnotationArgument.StringValue
+import kotlinx.metadata.KmAnnotationArgument.UByteValue
+import kotlinx.metadata.KmAnnotationArgument.UIntValue
+import kotlinx.metadata.KmAnnotationArgument.ULongValue
+import kotlinx.metadata.KmAnnotationArgument.UShortValue
+
+@KotlinPoetMetadataPreview
+internal fun KmAnnotation.toAnnotationSpec(): AnnotationSpec {
+  val cn = createClassName(className)
+  return AnnotationSpec.builder(cn)
+    .apply {
+      arguments.forEach { (name, arg) ->
+        addMember("%L = %L", name, arg.toCodeBlock())
+      }
+    }
+    .tag(this)
+    .build()
+}
+
+@OptIn(ExperimentalUnsignedTypes::class)
+@KotlinPoetMetadataPreview
+internal fun KmAnnotationArgument.toCodeBlock(): CodeBlock {
+  return when (this) {
+    is ByteValue -> CodeBlock.of("%L", value)
+    is CharValue -> CodeBlock.of("'%L'", value)
+    is ShortValue -> CodeBlock.of("%L", value)
+    is IntValue -> CodeBlock.of("%L", value)
+    is LongValue -> CodeBlock.of("%LL", value)
+    is FloatValue -> CodeBlock.of("%LF", value)
+    is DoubleValue -> CodeBlock.of("%L", value)
+    is BooleanValue -> CodeBlock.of("%L", value)
+    is UByteValue -> CodeBlock.of("%Lu", value)
+    is UShortValue -> CodeBlock.of("%Lu", value)
+    is UIntValue -> CodeBlock.of("%Lu", value)
+    is ULongValue -> CodeBlock.of("%Lu", value)
+    is StringValue -> CodeBlock.of("%S", value)
+    is KClassValue -> CodeBlock.of("%T::class", createClassName(className))
+    is EnumValue -> CodeBlock.of("%T.%L", createClassName(enumClassName), enumEntryName)
+    is AnnotationValue -> CodeBlock.of("%L", annotation.toAnnotationSpec())
+    is ArrayValue -> elements.map { it.toCodeBlock() }.joinToCode(", ", "[", "]")
+  }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt
new file mode 100644
index 0000000..6eb40a8
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+@file:JvmName("FacadeFile")
+@file:FileAnnotation("file annotations!")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import kotlin.annotation.AnnotationTarget.FILE
+
+@Target(FILE)
+annotation class FileAnnotation(val value: String)
+
+@JvmName("jvmStaticFunction")
+fun jvmNameFunction() {
+}
+
+fun regularFun() {
+}
+
+@Synchronized
+fun synchronizedFun() {
+}
+
+@JvmOverloads
+fun jvmOverloads(
+  param1: String,
+  optionalParam2: String = "",
+  nullableParam3: String? = null,
+) {
+}
+
+val BOOL_PROP = false
+val BINARY_PROP = 0b00001011
+val INT_PROP = 1
+val UNDERSCORES_PROP = 1_000_000
+val HEX_PROP = 0x0F
+val UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+val LONG_PROP = 1L
+val FLOAT_PROP = 1.0f
+val DOUBLE_PROP = 1.0
+val STRING_PROP = "prop"
+var VAR_BOOL_PROP = false
+var VAR_BINARY_PROP = 0b00001011
+var VAR_INT_PROP = 1
+var VAR_UNDERSCORES_PROP = 1_000_000
+var VAR_HEX_PROP = 0x0F
+var VAR_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+var VAR_LONG_PROP = 1L
+var VAR_FLOAT_PROP = 1.0f
+var VAR_DOUBLE_PROP = 1.0
+var VAR_STRING_PROP = "prop"
+
+const val CONST_BOOL_PROP = false
+const val CONST_BINARY_PROP = 0b00001011
+const val CONST_INT_PROP = 1
+const val CONST_UNDERSCORES_PROP = 1_000_000
+const val CONST_HEX_PROP = 0x0F
+const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+const val CONST_LONG_PROP = 1L
+const val CONST_FLOAT_PROP = 1.0f
+const val CONST_DOUBLE_PROP = 1.0
+const val CONST_STRING_PROP = "prop"
+
+@JvmField
+@JvmSynthetic
+val syntheticFieldProperty: kotlin.String? = null
+
+@field:JvmSynthetic
+val syntheticProperty: kotlin.String? = null
+
+@get:JvmSynthetic
+val syntheticPropertyGet: kotlin.String? = null
+
+@get:JvmSynthetic
+@set:JvmSynthetic
+var syntheticPropertyGetAndSet: kotlin.String? = null
+
+@set:JvmSynthetic
+var syntheticPropertySet: kotlin.String? = null
+
+typealias FacadeTypeAliasName = String
+typealias FacadeGenericTypeAlias = List<String>
+typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt
new file mode 100644
index 0000000..adea853
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE
+import org.junit.Test
+
+@KotlinPoetMetadataPreview
+class FacadeFileTest : MultiClassInspectorTest() {
+
+  @IgnoreForHandlerType(
+    handlerType = ELEMENTS,
+    reason = "Elements can detect JvmOverloads, JvmName not possible in reflection",
+  )
+  @Test
+  fun facadeFile_reflective() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.FacadeFile",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("FacadeFile")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      @file:JvmName(name = "FacadeFile")
+      @file:FileAnnotation(value = "file annotations!")
+
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import com.squareup.kotlinpoet.metadata.specs.FileAnnotation
+      import kotlin.Boolean
+      import kotlin.Double
+      import kotlin.Float
+      import kotlin.Int
+      import kotlin.Long
+      import kotlin.String
+      import kotlin.Unit
+      import kotlin.collections.List
+      import kotlin.jvm.JvmField
+      import kotlin.jvm.JvmName
+      import kotlin.jvm.JvmSynthetic
+      import kotlin.jvm.Synchronized
+
+      @JvmName(name = "jvmStaticFunction")
+      public fun jvmNameFunction(): Unit {
+      }
+
+      public fun jvmOverloads(
+        param1: String,
+        optionalParam2: String = throw NotImplementedError("Stub!"),
+        nullableParam3: String? = throw NotImplementedError("Stub!"),
+      ): Unit {
+      }
+
+      public fun regularFun(): Unit {
+      }
+
+      @Synchronized
+      public fun synchronizedFun(): Unit {
+      }
+
+      public val BINARY_PROP: Int = 11
+
+      public val BOOL_PROP: Boolean = false
+
+      public const val CONST_BINARY_PROP: Int = 11
+
+      public const val CONST_BOOL_PROP: Boolean = false
+
+      public const val CONST_DOUBLE_PROP: Double = 1.0
+
+      public const val CONST_FLOAT_PROP: Float = 1.0F
+
+      public const val CONST_HEX_PROP: Int = 15
+
+      public const val CONST_INT_PROP: Int = 1
+
+      public const val CONST_LONG_PROP: Long = 1L
+
+      public const val CONST_STRING_PROP: String = "prop"
+
+      public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+      public const val CONST_UNDERSCORES_PROP: Int = 1_000_000
+
+      public val DOUBLE_PROP: Double = 1.0
+
+      public val FLOAT_PROP: Float = 1.0F
+
+      public val HEX_PROP: Int = 15
+
+      public val INT_PROP: Int = 1
+
+      public val LONG_PROP: Long = 1L
+
+      public val STRING_PROP: String = "prop"
+
+      public val UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+      public val UNDERSCORES_PROP: Int = 1_000_000
+
+      public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+      public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+      public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+      public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+      public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!")
+
+      public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+      public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+      @field:JvmSynthetic
+      @JvmField
+      public val syntheticFieldProperty: String? = null
+
+      @field:JvmSynthetic
+      public val syntheticProperty: String? = null
+
+      @get:JvmSynthetic
+      public val syntheticPropertyGet: String? = null
+
+      @get:JvmSynthetic
+      @set:JvmSynthetic
+      public var syntheticPropertyGetAndSet: String? = null
+
+      @set:JvmSynthetic
+      public var syntheticPropertySet: String? = null
+
+      public typealias FacadeGenericTypeAlias = List<String>
+
+      public typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
+
+      public typealias FacadeTypeAliasName = String
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    handlerType = REFLECTIVE,
+    reason = "Elements can detect JvmOverloads, JvmName not possible in reflection",
+  )
+  @Test
+  fun facadeFile_elements() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.FacadeFile",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("FacadeFile")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      @file:FileAnnotation(value = "file annotations!")
+      @file:JvmName(name = "FacadeFile")
+
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import com.squareup.kotlinpoet.metadata.specs.FileAnnotation
+      import kotlin.Boolean
+      import kotlin.Double
+      import kotlin.Float
+      import kotlin.Int
+      import kotlin.Long
+      import kotlin.String
+      import kotlin.Unit
+      import kotlin.collections.List
+      import kotlin.jvm.JvmName
+      import kotlin.jvm.JvmOverloads
+      import kotlin.jvm.JvmSynthetic
+      import kotlin.jvm.Synchronized
+
+      @JvmName(name = "jvmStaticFunction")
+      public fun jvmNameFunction(): Unit {
+      }
+
+      @JvmOverloads
+      public fun jvmOverloads(
+        param1: String,
+        optionalParam2: String = throw NotImplementedError("Stub!"),
+        nullableParam3: String? = throw NotImplementedError("Stub!"),
+      ): Unit {
+      }
+
+      public fun regularFun(): Unit {
+      }
+
+      @Synchronized
+      public fun synchronizedFun(): Unit {
+      }
+
+      public val BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+      public val BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+      public const val CONST_BINARY_PROP: Int = 11
+
+      public const val CONST_BOOL_PROP: Boolean = false
+
+      public const val CONST_DOUBLE_PROP: Double = 1.0
+
+      public const val CONST_FLOAT_PROP: Float = 1.0F
+
+      public const val CONST_HEX_PROP: Int = 15
+
+      public const val CONST_INT_PROP: Int = 1
+
+      public const val CONST_LONG_PROP: Long = 1L
+
+      public const val CONST_STRING_PROP: String = "prop"
+
+      public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+      public const val CONST_UNDERSCORES_PROP: Int = 1_000_000
+
+      public val DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+      public val FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+      public val HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+      public val INT_PROP: Int = throw NotImplementedError("Stub!")
+
+      public val LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+      public val STRING_PROP: String = throw NotImplementedError("Stub!")
+
+      public val UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+      public val UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+      public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+      public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+      public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!")
+
+      public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+      public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!")
+
+      public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+      public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+      @field:JvmSynthetic
+      public val syntheticFieldProperty: String? = null
+
+      @field:JvmSynthetic
+      public val syntheticProperty: String? = null
+
+      @get:JvmSynthetic
+      public val syntheticPropertyGet: String? = null
+
+      @get:JvmSynthetic
+      @set:JvmSynthetic
+      public var syntheticPropertyGetAndSet: String? = null
+
+      @set:JvmSynthetic
+      public var syntheticPropertySet: String? = null
+
+      public typealias FacadeGenericTypeAlias = List<String>
+
+      public typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
+
+      public typealias FacadeTypeAliasName = String
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    handlerType = ELEMENTS,
+    reason = "JvmName not possible in reflection",
+  )
+  @Test
+  fun noJvmName_reflective() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import kotlin.String
+
+      public val prop: String = ""
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    handlerType = REFLECTIVE,
+    reason = "JvmName not possible in reflection",
+  )
+  @Test
+  fun noJvmName_elements() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import kotlin.String
+
+      public val prop: String = throw NotImplementedError("Stub!")
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    handlerType = ELEMENTS,
+    reason = "JvmName not possible in reflection",
+  )
+  @Test
+  fun jvmName_with_kt_reflective() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.JvmNameKt",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("JvmName")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import kotlin.String
+
+      public val prop2: String = ""
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    handlerType = REFLECTIVE,
+    reason = "JvmName not possible in reflection",
+  )
+  @Test
+  fun jvmName_with_kt_elements() {
+    val fileSpec = Class.forName(
+      "com.squareup.kotlinpoet.metadata.specs.JvmNameKt",
+    ).kotlin.toFileSpecWithTestHandler()
+    assertThat(fileSpec.name).isEqualTo("JvmName")
+    //language=kotlin
+    assertThat(fileSpec.trimmedToString()).isEqualTo(
+      """
+      @file:JvmName(name = "JvmNameKt")
+
+      package com.squareup.kotlinpoet.metadata.specs
+
+      import kotlin.String
+      import kotlin.jvm.JvmName
+
+      public val prop2: String = throw NotImplementedError("Stub!")
+      """.trimIndent(),
+    )
+  }
+}
+
+private fun FileSpec.trimmedToString(): String {
+  return buildString { writeTo(this) }.trim()
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt
new file mode 100644
index 0000000..008b36f
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+@file:JvmName("JvmNameKt")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+val prop2: String = ""
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt
new file mode 100644
index 0000000..c7928b4
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlin.test.Test
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue
+import kotlinx.metadata.KmAnnotationArgument.BooleanValue
+import kotlinx.metadata.KmAnnotationArgument.ByteValue
+import kotlinx.metadata.KmAnnotationArgument.CharValue
+import kotlinx.metadata.KmAnnotationArgument.DoubleValue
+import kotlinx.metadata.KmAnnotationArgument.EnumValue
+import kotlinx.metadata.KmAnnotationArgument.FloatValue
+import kotlinx.metadata.KmAnnotationArgument.IntValue
+import kotlinx.metadata.KmAnnotationArgument.KClassValue
+import kotlinx.metadata.KmAnnotationArgument.LongValue
+import kotlinx.metadata.KmAnnotationArgument.ShortValue
+import kotlinx.metadata.KmAnnotationArgument.StringValue
+import kotlinx.metadata.KmAnnotationArgument.UByteValue
+import kotlinx.metadata.KmAnnotationArgument.UIntValue
+import kotlinx.metadata.KmAnnotationArgument.ULongValue
+import kotlinx.metadata.KmAnnotationArgument.UShortValue
+
+@OptIn(ExperimentalUnsignedTypes::class)
+@KotlinPoetMetadataPreview
+class KmAnnotationsTest {
+
+  @Test fun noMembers() {
+    val annotation = KmAnnotation("test/NoMembersAnnotation", emptyMap())
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.NoMembersAnnotation
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun byteValue() {
+    val annotation = KmAnnotation(
+      "test/ByteValueAnnotation",
+      mapOf("value" to ByteValue(2)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.ByteValueAnnotation(value = 2)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun charValue() {
+    val annotation = KmAnnotation(
+      "test/CharValueAnnotation",
+      mapOf("value" to CharValue('2')),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.CharValueAnnotation(value = '2')
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun shortValue() {
+    val annotation = KmAnnotation(
+      "test/ShortValueAnnotation",
+      mapOf("value" to ShortValue(2)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.ShortValueAnnotation(value = 2)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun intValue() {
+    val annotation = KmAnnotation(
+      "test/IntValueAnnotation",
+      mapOf("value" to IntValue(2)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.IntValueAnnotation(value = 2)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun longValue() {
+    val annotation = KmAnnotation(
+      "test/LongValueAnnotation",
+      mapOf("value" to LongValue(2L)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.LongValueAnnotation(value = 2L)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun floatValue() {
+    val annotation = KmAnnotation(
+      "test/FloatValueAnnotation",
+      mapOf("value" to FloatValue(2.0F)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.FloatValueAnnotation(value = 2.0F)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun doubleValue() {
+    val annotation = KmAnnotation(
+      "test/DoubleValueAnnotation",
+      mapOf("value" to DoubleValue(2.0)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.DoubleValueAnnotation(value = 2.0)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun booleanValue() {
+    val annotation = KmAnnotation(
+      "test/BooleanValueAnnotation",
+      mapOf("value" to BooleanValue(true)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.BooleanValueAnnotation(value = true)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun uByteValue() {
+    val annotation = KmAnnotation(
+      "test/UByteValueAnnotation",
+      mapOf("value" to UByteValue(2u)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.UByteValueAnnotation(value = 2u)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun uShortValue() {
+    val annotation = KmAnnotation(
+      "test/UShortValueAnnotation",
+      mapOf("value" to UShortValue(2u)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.UShortValueAnnotation(value = 2u)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun uIntValue() {
+    val annotation = KmAnnotation(
+      "test/UIntValueAnnotation",
+      mapOf("value" to UIntValue(2u)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.UIntValueAnnotation(value = 2u)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun uLongValue() {
+    val annotation = KmAnnotation(
+      "test/ULongValueAnnotation",
+      mapOf("value" to ULongValue(2u)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.ULongValueAnnotation(value = 2u)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun stringValue() {
+    val annotation = KmAnnotation(
+      "test/StringValueAnnotation",
+      mapOf("value" to StringValue("taco")),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.StringValueAnnotation(value = "taco")
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun kClassValue() {
+    val annotation = KmAnnotation(
+      "test/KClassValueAnnotation",
+      mapOf("value" to KClassValue("test/OtherClass", 0)),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.KClassValueAnnotation(value = test.OtherClass::class)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun enumValue() {
+    val annotation = KmAnnotation(
+      "test/EnumValueAnnotation",
+      mapOf("value" to EnumValue("test/OtherClass", "VALUE")),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.EnumValueAnnotation(value = test.OtherClass.VALUE)
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun annotationValue() {
+    val annotation = KmAnnotation(
+      "test/AnnotationValueAnnotation",
+      mapOf(
+        "value" to AnnotationValue(
+          KmAnnotation("test/OtherAnnotation", mapOf("value" to StringValue("Hello!"))),
+        ),
+      ),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.AnnotationValueAnnotation(value = test.OtherAnnotation(value = "Hello!"))
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun arrayValue() {
+    val annotation = KmAnnotation(
+      "test/ArrayValueAnnotation",
+      mapOf("value" to ArrayValue(listOf(IntValue(1), IntValue(2), IntValue(3)))),
+    )
+    assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+      """
+      @test.ArrayValueAnnotation(value = [1, 2, 3])
+      """.trimIndent(),
+    )
+  }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt
new file mode 100644
index 0000000..9198dbc
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt
@@ -0,0 +1,2250 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+@file:OptIn(KotlinPoetMetadataPreview::class)
+@file:Suppress(
+  "DEPRECATION",
+  "NOTHING_TO_INLINE",
+  "RedundantSuspendModifier",
+  "RedundantUnitReturnType",
+  "RedundantVisibilityModifier",
+  "RemoveEmptyPrimaryConstructor",
+  "RemoveRedundantQualifierName",
+  "UNUSED_PARAMETER",
+  "unused",
+)
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE
+import com.squareup.kotlinpoet.tag
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.TYPE
+import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER
+import kotlin.properties.Delegates
+import kotlin.test.fail
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmValueParameter
+import org.junit.Ignore
+import org.junit.Test
+
+class KotlinPoetMetadataSpecsTest : MultiClassInspectorTest() {
+
+  @Test
+  fun constructorData() {
+    val typeSpec = ConstructorClass::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ConstructorClass(
+        public val foo: kotlin.String,
+        vararg bar: kotlin.Int,
+      ) {
+        public constructor(bar: kotlin.Int)
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class ConstructorClass(val foo: String, vararg bar: Int) {
+    // Secondary constructors are ignored, so we expect this constructor to not be the one picked
+    // up in the test.
+    constructor(bar: Int) : this("defaultFoo")
+  }
+
+  @Test
+  fun supertype() {
+    val typeSpec = Supertype::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Supertype() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseType(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseInterface
+      """.trimIndent(),
+    )
+  }
+
+  abstract class BaseType
+  interface BaseInterface
+  class Supertype : BaseType(), BaseInterface
+
+  @IgnoreForHandlerType(
+    reason = "Elements properly resolves the string constant",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun propertiesReflective() {
+    val typeSpec = Properties::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Properties() {
+        public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!")
+
+        public val bar: kotlin.String? = null
+
+        public var baz: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val foo: kotlin.String = throw NotImplementedError("Stub!")
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Elements properly resolves the string constant",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun propertiesElements() {
+    val typeSpec = Properties::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Properties() {
+        public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!")
+
+        public val bar: kotlin.String? = null
+
+        public var baz: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val foo: kotlin.String = throw NotImplementedError("Stub!")
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Properties {
+    val foo: String = ""
+    val bar: String? = null
+    var baz: Int = 0
+    var aList: List<Int> = emptyList()
+  }
+
+  @Test
+  fun companionObject() {
+    val typeSpec = CompanionObject::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class CompanionObject() {
+        public companion object
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class CompanionObject {
+    companion object
+  }
+
+  @Test
+  fun namedCompanionObject() {
+    val typeSpec = NamedCompanionObject::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class NamedCompanionObject() {
+        public companion object Named
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class NamedCompanionObject {
+    companion object Named
+  }
+
+  @Test
+  fun generics() {
+    val typeSpec = Generics::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Generics<out T, in R, V>(
+        public val genericInput: T,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  class Generics<out T, in R, V>(val genericInput: T)
+
+  @Test
+  fun typeAliases() {
+    val typeSpec = TypeAliases::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class TypeAliases(
+        public val foo: com.squareup.kotlinpoet.metadata.specs.TypeAliasName,
+        public val bar: com.squareup.kotlinpoet.metadata.specs.GenericTypeAlias,
+      )
+      """.trimIndent(),
+    )
+
+    val fooPropertyType = typeSpec.propertySpecs.first { it.name == "foo" }.type
+    val fooAliasData = fooPropertyType.tag<TypeAliasTag>()
+    checkNotNull(fooAliasData)
+    assertThat(fooAliasData.abbreviatedType).isEqualTo(STRING)
+
+    val barPropertyType = typeSpec.propertySpecs.first { it.name == "bar" }.type
+    val barAliasData = barPropertyType.tag<TypeAliasTag>()
+    checkNotNull(barAliasData)
+    assertThat(barAliasData.abbreviatedType).isEqualTo(LIST.parameterizedBy(STRING))
+  }
+
+  class TypeAliases(val foo: TypeAliasName, val bar: GenericTypeAlias)
+
+  @Test
+  fun propertyMutability() {
+    val typeSpec = PropertyMutability::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class PropertyMutability(
+        public val foo: kotlin.String,
+        public var mutableFoo: kotlin.String,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  class PropertyMutability(val foo: String, var mutableFoo: String)
+
+  @Test
+  fun collectionMutability() {
+    val typeSpec = CollectionMutability::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class CollectionMutability(
+        public val immutableList: kotlin.collections.List<kotlin.String>,
+        public val mutableList: kotlin.collections.MutableList<kotlin.String>,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  class CollectionMutability(val immutableList: List<String>, val mutableList: MutableList<String>)
+
+  @Test
+  fun suspendTypes() {
+    val typeSpec = SuspendTypes::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class SuspendTypes() {
+        public val testProp: suspend (kotlin.Int, kotlin.Long) -> kotlin.String = throw NotImplementedError("Stub!")
+
+        public suspend fun testComplexSuspendFun(body: suspend (kotlin.Int, suspend (kotlin.Long) -> kotlin.String) -> kotlin.String): kotlin.Unit {
+        }
+
+        public fun testFun(body: suspend (kotlin.Int, kotlin.Long) -> kotlin.String): kotlin.Unit {
+        }
+
+        public suspend fun testSuspendFun(param1: kotlin.String): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class SuspendTypes {
+    val testProp: suspend (Int, Long) -> String = { _, _ -> "" }
+
+    fun testFun(body: suspend (Int, Long) -> String) {
+    }
+
+    suspend fun testSuspendFun(param1: String) {
+    }
+
+    suspend fun testComplexSuspendFun(body: suspend (Int, suspend (Long) -> String) -> String) {
+    }
+  }
+
+  @Test
+  fun parameters() {
+    val typeSpec = Parameters::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Parameters() {
+        public inline fun hasDefault(param1: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit {
+        }
+
+        public inline fun `inline`(crossinline param1: () -> kotlin.String): kotlin.Unit {
+        }
+
+        public inline fun `noinline`(noinline param1: () -> kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Parameters {
+    inline fun inline(crossinline param1: () -> String) {
+    }
+
+    inline fun noinline(noinline param1: () -> String): String {
+      return ""
+    }
+
+    inline fun hasDefault(param1: String = "Nope") {
+    }
+  }
+
+  @Test
+  fun lambdaReceiver() {
+    val typeSpec = LambdaReceiver::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class LambdaReceiver() {
+        public fun lambdaReceiver(block: kotlin.String.() -> kotlin.Unit): kotlin.Unit {
+        }
+
+        public fun lambdaReceiver2(block: kotlin.String.(kotlin.Int) -> kotlin.Unit): kotlin.Unit {
+        }
+
+        public fun lambdaReceiver3(block: kotlin.String.(kotlin.Int, kotlin.String) -> kotlin.Unit): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class LambdaReceiver {
+    fun lambdaReceiver(block: String.() -> Unit) {
+    }
+    fun lambdaReceiver2(block: String.(Int) -> Unit) {
+    }
+    fun lambdaReceiver3(block: String.(Int, String) -> Unit) {
+    }
+  }
+
+  @Test
+  fun nestedTypeAlias() {
+    val typeSpec = NestedTypeAliasTest::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class NestedTypeAliasTest() {
+        public val prop: com.squareup.kotlinpoet.metadata.specs.NestedTypeAlias = throw NotImplementedError("Stub!")
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class NestedTypeAliasTest {
+    val prop: NestedTypeAlias = listOf(listOf(""))
+  }
+
+  @Test
+  fun inlineClass() {
+    val typeSpec = InlineClass::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @kotlin.jvm.JvmInline
+      public value class InlineClass(
+        public val `value`: kotlin.String,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun valueClass() {
+    val typeSpec = ValueClass::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @kotlin.jvm.JvmInline
+      public value class ValueClass(
+        public val `value`: kotlin.String,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun functionReferencingTypeParam() {
+    val typeSpec = FunctionsReferencingTypeParameters::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class FunctionsReferencingTypeParameters<T>() {
+        public fun test(`param`: T): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class FunctionsReferencingTypeParameters<T> {
+    fun test(param: T) {
+    }
+  }
+
+  @Test
+  fun overriddenThings() {
+    val typeSpec = OverriddenThings::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public abstract class OverriddenThings() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsInterface {
+        public override var openProp: kotlin.String = throw NotImplementedError("Stub!")
+
+        public override var openPropInterface: kotlin.String = throw NotImplementedError("Stub!")
+
+        public override fun openFunction(): kotlin.Unit {
+        }
+
+        public override fun openFunctionInterface(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  abstract class OverriddenThingsBase {
+    abstract var openProp: String
+
+    abstract fun openFunction()
+  }
+
+  interface OverriddenThingsInterface {
+    var openPropInterface: String
+
+    fun openFunctionInterface()
+  }
+
+  abstract class OverriddenThings : OverriddenThingsBase(), OverriddenThingsInterface {
+    override var openProp: String = ""
+    override var openPropInterface: String = ""
+
+    override fun openFunction() {
+    }
+
+    override fun openFunctionInterface() {
+    }
+  }
+
+  @Test
+  fun delegatedProperties() {
+    val typeSpec = DelegatedProperties::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class DelegatedProperties() {
+        /**
+         * Note: delegation is ABI stub only and not guaranteed to match source code.
+         */
+        public val immutable: kotlin.String by kotlin.lazy { throw NotImplementedError("Stub!") }
+
+        /**
+         * Note: delegation is ABI stub only and not guaranteed to match source code.
+         */
+        public val immutableNullable: kotlin.String? by kotlin.lazy { throw NotImplementedError("Stub!") }
+
+        /**
+         * Note: delegation is ABI stub only and not guaranteed to match source code.
+         */
+        public var mutable: kotlin.String by kotlin.properties.Delegates.notNull()
+
+        /**
+         * Note: delegation is ABI stub only and not guaranteed to match source code.
+         */
+        public var mutableNullable: kotlin.String? by kotlin.properties.Delegates.observable(null) { _, _, _ -> }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class DelegatedProperties {
+    val immutable: String by lazy { "" }
+    val immutableNullable: String? by lazy { "" }
+    var mutable: String by Delegates.notNull()
+    var mutableNullable: String? by Delegates.observable(null) { _, _, _ -> }
+  }
+
+  @Ignore("Need to be able to know about class delegation in metadata")
+  @Test
+  fun classDelegation() {
+    val typeSpec = ClassDelegation::class.toTypeSpecWithTestHandler()
+
+    // TODO Assert this also excludes functions handled by the delegate
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ClassDelegation<T>(
+        delegate: List<T>
+      ): List<T> by delegate
+      """.trimIndent(),
+    )
+  }
+
+  class ClassDelegation<T>(delegate: List<T>) : List<T> by delegate
+
+  @Test
+  fun simpleEnum() {
+    val typeSpec = SimpleEnum::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public enum class SimpleEnum() {
+        FOO,
+        BAR,
+        BAZ,
+      }
+      """.trimIndent(),
+    )
+  }
+
+  enum class SimpleEnum {
+    FOO, BAR, BAZ
+  }
+
+  @Test
+  fun complexEnum() {
+    val typeSpec = ComplexEnum::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public enum class ComplexEnum(
+        public val `value`: kotlin.String,
+      ) {
+        FOO {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        BAR {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        BAZ {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        ;
+      }
+      """.trimIndent(),
+    )
+  }
+
+  enum class ComplexEnum(val value: String) {
+    FOO("foo") {
+      override fun toString(): String {
+        return "foo1"
+      }
+    },
+    BAR("bar") {
+      override fun toString(): String {
+        return "bar1"
+      }
+    },
+    BAZ("baz") {
+      override fun toString(): String {
+        return "baz1"
+      }
+    },
+  }
+
+  @Test
+  fun enumWithAnnotation() {
+    val typeSpec = EnumWithAnnotation::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public enum class EnumWithAnnotation() {
+        FOO,
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+        BAR,
+        BAZ,
+      }
+      """.trimIndent(),
+    )
+  }
+
+  enum class EnumWithAnnotation {
+    FOO, @FieldAnnotation
+    BAR, BAZ
+  }
+
+  @Test
+  fun complexEnumWithAnnotation() {
+    val typeSpec = ComplexEnumWithAnnotation::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public enum class ComplexEnumWithAnnotation(
+        public val `value`: kotlin.String,
+      ) {
+        FOO {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+        BAR {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        BAZ {
+          public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+        },
+        ;
+      }
+      """.trimIndent(),
+    )
+  }
+
+  enum class ComplexEnumWithAnnotation(val value: String) {
+    FOO("foo") {
+      override fun toString(): String {
+        return "foo1"
+      }
+    },
+
+    @FieldAnnotation
+    BAR("bar") {
+      override fun toString(): String {
+        return "bar1"
+      }
+    },
+    BAZ("baz") {
+      override fun toString(): String {
+        return "baz1"
+      }
+    },
+  }
+
+  @Test
+  fun interfaces() {
+    val testInterfaceSpec = TestInterface::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(testInterfaceSpec.trimmedToString()).isEqualTo(
+      """
+      public interface TestInterface {
+        public fun complex(input: kotlin.String, input2: kotlin.String = throw NotImplementedError("Stub!")): kotlin.String = throw NotImplementedError("Stub!")
+
+        public fun hasDefault(): kotlin.Unit {
+        }
+
+        public fun hasDefaultMultiParam(input: kotlin.String, input2: kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+
+        public fun hasDefaultSingleParam(input: kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+
+        @kotlin.jvm.JvmDefault
+        public fun hasJvmDefault(): kotlin.Unit {
+        }
+
+        public fun noDefault(): kotlin.Unit
+
+        public fun noDefaultWithInput(input: kotlin.String): kotlin.Unit
+
+        public fun noDefaultWithInputDefault(input: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit
+      }
+      """.trimIndent(),
+    )
+
+    val subInterfaceSpec = SubInterface::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(subInterfaceSpec.trimmedToString()).isEqualTo(
+      """
+      public interface SubInterface : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TestInterface {
+        public override fun hasDefault(): kotlin.Unit {
+        }
+
+        @kotlin.jvm.JvmDefault
+        public override fun hasJvmDefault(): kotlin.Unit {
+        }
+
+        public fun subInterfaceFunction(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+
+    val implSpec = TestSubInterfaceImpl::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(implSpec.trimmedToString()).isEqualTo(
+      """
+      public class TestSubInterfaceImpl() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SubInterface {
+        public override fun noDefault(): kotlin.Unit {
+        }
+
+        public override fun noDefaultWithInput(input: kotlin.String): kotlin.Unit {
+        }
+
+        public override fun noDefaultWithInputDefault(input: kotlin.String): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  interface TestInterface {
+
+    fun noDefault()
+
+    fun noDefaultWithInput(input: String)
+
+    fun noDefaultWithInputDefault(input: String = "")
+
+    @JvmDefault
+    fun hasJvmDefault() {
+    }
+
+    fun hasDefault() {
+    }
+
+    fun hasDefaultSingleParam(input: String): String {
+      return "1234"
+    }
+
+    fun hasDefaultMultiParam(input: String, input2: String): String {
+      return "1234"
+    }
+
+    fun complex(input: String, input2: String = ""): String {
+      return "5678"
+    }
+  }
+
+  interface SubInterface : TestInterface {
+    fun subInterfaceFunction() {
+    }
+
+    @JvmDefault
+    override fun hasJvmDefault() {
+      super.hasJvmDefault()
+    }
+
+    override fun hasDefault() {
+      super.hasDefault()
+    }
+  }
+
+  class TestSubInterfaceImpl : SubInterface {
+    override fun noDefault() {
+    }
+
+    override fun noDefaultWithInput(input: String) {
+    }
+
+    override fun noDefaultWithInputDefault(input: String) {
+    }
+  }
+
+  @Test
+  fun backwardReferencingTypeVars() {
+    val typeSpec = BackwardReferencingTypeVars::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public interface BackwardReferencingTypeVars<T> : kotlin.collections.List<kotlin.collections.Set<T>>
+      """.trimIndent(),
+    )
+  }
+
+  interface BackwardReferencingTypeVars<T> : List<Set<T>>
+
+  @Test
+  fun taggedTypes() {
+    val typeSpec = TaggedTypes::class.toTypeSpecWithTestHandler()
+    assertThat(typeSpec.tag<KmClass>()).isNotNull()
+
+    val constructorSpec = typeSpec.primaryConstructor ?: fail("No constructor found!")
+    assertThat(constructorSpec.tag<KmConstructor>()).isNotNull()
+
+    val parameterSpec = constructorSpec.parameters[0]
+    assertThat(parameterSpec.tag<KmValueParameter>()).isNotNull()
+
+    val typeVar = typeSpec.typeVariables[0]
+    assertThat(typeVar.tag<KmTypeParameter>()).isNotNull()
+
+    val funSpec = typeSpec.funSpecs[0]
+    assertThat(funSpec.tag<KmFunction>()).isNotNull()
+
+    val propertySpec = typeSpec.propertySpecs[0]
+    assertThat(propertySpec.tag<KmProperty>()).isNotNull()
+  }
+
+  class TaggedTypes<T>(val param: T) {
+    val property: String = ""
+
+    fun function() {
+    }
+  }
+
+  @Test
+  fun annotations() {
+    val typeSpec = MyAnnotation::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public annotation class MyAnnotation(
+        public val `value`: kotlin.String,
+      )
+      """.trimIndent(),
+    )
+  }
+
+  annotation class MyAnnotation(val value: String)
+
+  @Test
+  fun functionTypeArgsSupersedeClass() {
+    val typeSpec = GenericClass::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class GenericClass<T>() {
+        public fun <T> functionAlsoWithT(`param`: T): kotlin.Unit {
+        }
+
+        public fun <R> functionWithADifferentType(`param`: R): kotlin.Unit {
+        }
+
+        public fun functionWithT(`param`: T): kotlin.Unit {
+        }
+
+        /**
+         * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+         */
+        public inline fun <reified T> `reified`(`param`: T): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+
+    val func1TypeVar = typeSpec.funSpecs.find { it.name == "functionAlsoWithT" }!!.typeVariables.first()
+    val classTypeVar = typeSpec.typeVariables.first()
+
+    assertThat(func1TypeVar).isNotSameInstanceAs(classTypeVar)
+  }
+
+  class GenericClass<T> {
+    fun functionWithT(param: T) {
+    }
+    fun <T> functionAlsoWithT(param: T) {
+    }
+    fun <R> functionWithADifferentType(param: R) {
+    }
+
+    // Regression for https://github.com/square/kotlinpoet/issues/829
+    inline fun <reified T> reified(param: T) {
+    }
+  }
+
+  @Test
+  fun complexCompanionObject() {
+    val typeSpec = ComplexCompanionObject::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ComplexCompanionObject() {
+        public companion object ComplexObject : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionInterface
+      }
+      """.trimIndent(),
+    )
+  }
+
+  interface CompanionInterface
+  open class CompanionBase
+
+  class ComplexCompanionObject {
+    companion object ComplexObject : CompanionBase(), CompanionInterface
+  }
+
+  @IgnoreForHandlerType(
+    reason = "TODO Synthetic methods that hold annotations aren't visible in these tests",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun annotationsAreCopied() {
+    val typeSpec = AnnotationHolders::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class AnnotationHolders @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation constructor() {
+        @field:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+        public var `field`: kotlin.String? = null
+
+        @get:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.GetterAnnotation
+        public var getter: kotlin.String? = null
+
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.HolderAnnotation
+        @kotlin.jvm.JvmField
+        public var holder: kotlin.String? = null
+
+        @set:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SetterAnnotation
+        public var setter: kotlin.String? = null
+
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation
+        public constructor(`value`: kotlin.String)
+
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FunctionAnnotation
+        public fun function(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class AnnotationHolders @ConstructorAnnotation constructor() {
+
+    @ConstructorAnnotation
+    constructor(value: String) : this()
+
+    @field:FieldAnnotation
+    var field: String? = null
+
+    @get:GetterAnnotation
+    var getter: String? = null
+
+    @set:SetterAnnotation
+    var setter: String? = null
+
+    @HolderAnnotation
+    @JvmField
+    var holder: String? = null
+
+    @FunctionAnnotation
+    fun function() {
+    }
+  }
+
+  @Retention(RUNTIME)
+  annotation class ConstructorAnnotation
+
+  @Retention(RUNTIME)
+  annotation class FieldAnnotation
+
+  @Retention(RUNTIME)
+  annotation class GetterAnnotation
+
+  @Retention(RUNTIME)
+  annotation class SetterAnnotation
+
+  @Retention(RUNTIME)
+  annotation class HolderAnnotation
+
+  @Retention(RUNTIME)
+  annotation class FunctionAnnotation
+
+  @IgnoreForHandlerType(
+    reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun constantValuesElements() {
+    val typeSpec = Constants::class.toTypeSpecWithTestHandler()
+
+    // Note: formats like hex/binary/underscore are not available as formatted at runtime
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Constants(
+        public val `param`: kotlin.String = throw NotImplementedError("Stub!"),
+      ) {
+        public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+        public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!")
+
+        public val floatProp: kotlin.Float = throw NotImplementedError("Stub!")
+
+        public val hexProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val intProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val longProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+        public val stringProp: kotlin.String = throw NotImplementedError("Stub!")
+
+        public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+        public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public companion object {
+          public const val CONST_BINARY_PROP: kotlin.Int = 11
+
+          public const val CONST_BOOL_PROP: kotlin.Boolean = false
+
+          public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+          public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+          public const val CONST_HEX_PROP: kotlin.Int = 15
+
+          public const val CONST_INT_PROP: kotlin.Int = 1
+
+          public const val CONST_LONG_PROP: kotlin.Long = 1L
+
+          public const val CONST_STRING_PROP: kotlin.String = "prop"
+
+          public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+          public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_BINARY_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_FLOAT_PROP: kotlin.Float = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_HEX_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_INT_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_LONG_PROP: kotlin.Long = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_STRING_PROP: kotlin.String = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = throw NotImplementedError("Stub!")
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+        }
+      }
+      """.trimIndent(),
+    )
+
+    // TODO check with objects
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun constantValuesReflective() {
+    val typeSpec = Constants::class.toTypeSpecWithTestHandler()
+
+    // Note: formats like hex/binary/underscore are not available as formatted in elements
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Constants(
+        public val `param`: kotlin.String = throw NotImplementedError("Stub!"),
+      ) {
+        public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+        public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!")
+
+        public val floatProp: kotlin.Float = throw NotImplementedError("Stub!")
+
+        public val hexProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val intProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public val longProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+        public val stringProp: kotlin.String = throw NotImplementedError("Stub!")
+
+        public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+        public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+        public companion object {
+          public const val CONST_BINARY_PROP: kotlin.Int = 11
+
+          public const val CONST_BOOL_PROP: kotlin.Boolean = false
+
+          public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+          public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+          public const val CONST_HEX_PROP: kotlin.Int = 15
+
+          public const val CONST_INT_PROP: kotlin.Int = 1
+
+          public const val CONST_LONG_PROP: kotlin.Long = 1L
+
+          public const val CONST_STRING_PROP: kotlin.String = "prop"
+
+          public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+          public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_BINARY_PROP: kotlin.Int = 11
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = false
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_HEX_PROP: kotlin.Int = 15
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_INT_PROP: kotlin.Int = 1
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_LONG_PROP: kotlin.Long = 1L
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_STRING_PROP: kotlin.String = "prop"
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+          @kotlin.jvm.JvmStatic
+          public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Constants(
+    val param: String = "param",
+  ) {
+    val boolProp = false
+    val binaryProp = 0b00001011
+    val intProp = 1
+    val underscoresProp = 1_000_000
+    val hexProp = 0x0F
+    val underscoresHexProp = 0xFF_EC_DE_5E
+    val longProp = 1L
+    val floatProp = 1.0F
+    val doubleProp = 1.0
+    val stringProp = "prop"
+
+    companion object {
+      @JvmStatic val STATIC_CONST_BOOL_PROP = false
+
+      @JvmStatic val STATIC_CONST_BINARY_PROP = 0b00001011
+
+      @JvmStatic val STATIC_CONST_INT_PROP = 1
+
+      @JvmStatic val STATIC_CONST_UNDERSCORES_PROP = 1_000_000
+
+      @JvmStatic val STATIC_CONST_HEX_PROP = 0x0F
+
+      @JvmStatic val STATIC_CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+
+      @JvmStatic val STATIC_CONST_LONG_PROP = 1L
+
+      @JvmStatic val STATIC_CONST_FLOAT_PROP = 1.0f
+
+      @JvmStatic val STATIC_CONST_DOUBLE_PROP = 1.0
+
+      @JvmStatic val STATIC_CONST_STRING_PROP = "prop"
+
+      const val CONST_BOOL_PROP = false
+      const val CONST_BINARY_PROP = 0b00001011
+      const val CONST_INT_PROP = 1
+      const val CONST_UNDERSCORES_PROP = 1_000_000
+      const val CONST_HEX_PROP = 0x0F
+      const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+      const val CONST_LONG_PROP = 1L
+      const val CONST_FLOAT_PROP = 1.0f
+      const val CONST_DOUBLE_PROP = 1.0
+      const val CONST_STRING_PROP = "prop"
+    }
+  }
+
+  @Test
+  fun jvmAnnotations() {
+    val typeSpec = JvmAnnotations::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class JvmAnnotations() {
+        @get:kotlin.jvm.Synchronized
+        public val synchronizedGetProp: kotlin.String? = null
+
+        @set:kotlin.jvm.Synchronized
+        public var synchronizedSetProp: kotlin.String? = null
+
+        @kotlin.jvm.Transient
+        public val transientProp: kotlin.String? = null
+
+        @kotlin.jvm.Volatile
+        public var volatileProp: kotlin.String? = null
+
+        @kotlin.jvm.Synchronized
+        public fun synchronizedFun(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+
+    val interfaceSpec = JvmAnnotationsInterface::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(interfaceSpec.trimmedToString()).isEqualTo(
+      """
+      public interface JvmAnnotationsInterface {
+        @kotlin.jvm.JvmDefault
+        public fun defaultMethod(): kotlin.Unit {
+        }
+
+        public fun notDefaultMethod(): kotlin.Unit
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class JvmAnnotations {
+    @Transient val transientProp: String? = null
+
+    @Volatile var volatileProp: String? = null
+
+    @get:Synchronized val synchronizedGetProp: String? = null
+
+    @set:Synchronized var synchronizedSetProp: String? = null
+
+    @Synchronized
+    fun synchronizedFun() {
+    }
+  }
+
+  interface JvmAnnotationsInterface {
+    @JvmDefault
+    fun defaultMethod() {
+    }
+    fun notDefaultMethod()
+  }
+
+  @Test
+  fun nestedClasses() {
+    val typeSpec = NestedClasses::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class NestedClasses() {
+        public abstract class NestedClass<T>() : kotlin.collections.List<T>
+
+        public inner class NestedInnerClass()
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class NestedClasses {
+    abstract class NestedClass<T> : List<T>
+    inner class NestedInnerClass
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " +
+      "elements will not",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun jvmNamesReflective() {
+    val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class JvmNameData(
+        @get:kotlin.jvm.JvmName(name = "jvmParam")
+        public val `param`: kotlin.String,
+      ) {
+        @get:kotlin.jvm.JvmName(name = "jvmPropertyGet")
+        public val propertyGet: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+        @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+        public var propertyGetAndSet: kotlin.String? = null
+
+        @set:kotlin.jvm.JvmName(name = "jvmPropertySet")
+        public var propertySet: kotlin.String? = null
+
+        @kotlin.jvm.JvmName(name = "jvmFunction")
+        public fun function(): kotlin.Unit {
+        }
+
+        public interface InterfaceWithJvmName {
+          public companion object {
+            @get:kotlin.jvm.JvmName(name = "fooBoolJvm")
+            @kotlin.jvm.JvmStatic
+            public val FOO_BOOL: kotlin.Boolean = false
+
+            @kotlin.jvm.JvmName(name = "jvmStaticFunction")
+            @kotlin.jvm.JvmStatic
+            public fun staticFunction(): kotlin.Unit {
+            }
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " +
+      "elements will not",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun jvmNamesElements() {
+    val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class JvmNameData(
+        @get:kotlin.jvm.JvmName(name = "jvmParam")
+        public val `param`: kotlin.String,
+      ) {
+        @get:kotlin.jvm.JvmName(name = "jvmPropertyGet")
+        public val propertyGet: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+        @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+        public var propertyGetAndSet: kotlin.String? = null
+
+        @set:kotlin.jvm.JvmName(name = "jvmPropertySet")
+        public var propertySet: kotlin.String? = null
+
+        @kotlin.jvm.JvmName(name = "jvmFunction")
+        public fun function(): kotlin.Unit {
+        }
+
+        public interface InterfaceWithJvmName {
+          public companion object {
+            @get:kotlin.jvm.JvmName(name = "fooBoolJvm")
+            @kotlin.jvm.JvmStatic
+            public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+            @kotlin.jvm.JvmName(name = "jvmStaticFunction")
+            @kotlin.jvm.JvmStatic
+            public fun staticFunction(): kotlin.Unit {
+            }
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class JvmNameData(
+    @get:JvmName("jvmParam") val param: String,
+  ) {
+
+    @get:JvmName("jvmPropertyGet")
+    val propertyGet: String? = null
+
+    @set:JvmName("jvmPropertySet")
+    var propertySet: String? = null
+
+    @set:JvmName("jvmPropertyGetAndSet")
+    @get:JvmName("jvmPropertyGetAndSet")
+    var propertyGetAndSet: String? = null
+
+    @JvmName("jvmFunction")
+    fun function() {
+    }
+
+    // Interfaces can't have JvmName, but covering a potential edge case of having a companion
+    // object with JvmName elements. Also covers an edge case where constants have getters
+    interface InterfaceWithJvmName {
+      companion object {
+        @JvmStatic
+        @get:JvmName("fooBoolJvm")
+        val FOO_BOOL = false
+
+        @JvmName("jvmStaticFunction")
+        @JvmStatic
+        fun staticFunction() {
+        }
+      }
+    }
+  }
+
+  @IgnoreForHandlerType(
+    reason = "JvmOverloads is not runtime retained and thus not visible to reflection.",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun overloads() {
+    val typeSpec = Overloads::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Overloads @kotlin.jvm.JvmOverloads constructor(
+        public val param1: kotlin.String,
+        public val optionalParam2: kotlin.String = throw NotImplementedError("Stub!"),
+        public val nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"),
+      ) {
+        @kotlin.jvm.JvmOverloads
+        public fun testFunction(
+          param1: kotlin.String,
+          optionalParam2: kotlin.String = throw NotImplementedError("Stub!"),
+          nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"),
+        ): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Overloads @JvmOverloads constructor(
+    val param1: String,
+    val optionalParam2: String = "",
+    val nullableParam3: String? = null,
+  ) {
+    @JvmOverloads
+    fun testFunction(
+      param1: String,
+      optionalParam2: String = "",
+      nullableParam3: String? = null,
+    ) {
+    }
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Elements generates initializer values.",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun jvmFields_reflective() {
+    val typeSpec = Fields::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Fields(
+        @property:kotlin.jvm.JvmField
+        public val param1: kotlin.String,
+      ) {
+        @kotlin.jvm.JvmField
+        public val fieldProp: kotlin.String = throw NotImplementedError("Stub!")
+
+        public companion object {
+          @kotlin.jvm.JvmField
+          public val companionProp: kotlin.String = ""
+
+          public const val constCompanionProp: kotlin.String = ""
+
+          @kotlin.jvm.JvmStatic
+          public val staticCompanionProp: kotlin.String = ""
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Elements generates initializer values.",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun jvmFields_elements() {
+    val typeSpec = Fields::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Fields(
+        @property:kotlin.jvm.JvmField
+        public val param1: kotlin.String,
+      ) {
+        @kotlin.jvm.JvmField
+        public val fieldProp: kotlin.String = throw NotImplementedError("Stub!")
+
+        public companion object {
+          @kotlin.jvm.JvmField
+          public val companionProp: kotlin.String = throw NotImplementedError("Stub!")
+
+          public const val constCompanionProp: kotlin.String = ""
+
+          @kotlin.jvm.JvmStatic
+          public val staticCompanionProp: kotlin.String = throw NotImplementedError("Stub!")
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Fields(
+    @JvmField val param1: String,
+  ) {
+    @JvmField val fieldProp: String = ""
+
+    companion object {
+      @JvmField val companionProp: String = ""
+
+      @JvmStatic val staticCompanionProp: String = ""
+      const val constCompanionProp: String = ""
+    }
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Synthetic constructs aren't available in elements, so some information like " +
+      "JvmStatic can't be deduced.",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun synthetics_reflective() {
+    val typeSpec = Synthetics::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Synthetics(
+        @get:kotlin.jvm.JvmSynthetic
+        public val `param`: kotlin.String,
+      ) {
+        @field:kotlin.jvm.JvmSynthetic
+        public val fieldProperty: kotlin.String? = null
+
+        @field:kotlin.jvm.JvmSynthetic
+        public val `property`: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmSynthetic
+        public val propertyGet: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmSynthetic
+        @set:kotlin.jvm.JvmSynthetic
+        public var propertyGetAndSet: kotlin.String? = null
+
+        @set:kotlin.jvm.JvmSynthetic
+        public var propertySet: kotlin.String? = null
+
+        /**
+         * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+         */
+        @kotlin.jvm.JvmSynthetic
+        public fun function(): kotlin.Unit {
+        }
+
+        public interface InterfaceWithJvmName {
+          /**
+           * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+           */
+          @kotlin.jvm.JvmSynthetic
+          public fun interfaceFunction(): kotlin.Unit
+
+          public companion object {
+            @get:kotlin.jvm.JvmSynthetic
+            @kotlin.jvm.JvmStatic
+            public val FOO_BOOL: kotlin.Boolean = false
+
+            /**
+             * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+             */
+            @kotlin.jvm.JvmStatic
+            @kotlin.jvm.JvmSynthetic
+            public fun staticFunction(): kotlin.Unit {
+            }
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Synthetic constructs aren't available in elements, so some information like " +
+      "JvmStatic can't be deduced.",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun synthetics_elements() {
+    val typeSpec = Synthetics::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Synthetics(
+        @get:kotlin.jvm.JvmSynthetic
+        public val `param`: kotlin.String,
+      ) {
+        @field:kotlin.jvm.JvmSynthetic
+        public val fieldProperty: kotlin.String? = null
+
+        @field:kotlin.jvm.JvmSynthetic
+        public val `property`: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmSynthetic
+        public val propertyGet: kotlin.String? = null
+
+        @get:kotlin.jvm.JvmSynthetic
+        @set:kotlin.jvm.JvmSynthetic
+        public var propertyGetAndSet: kotlin.String? = null
+
+        @set:kotlin.jvm.JvmSynthetic
+        public var propertySet: kotlin.String? = null
+
+        /**
+         * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+         */
+        @kotlin.jvm.JvmSynthetic
+        public fun function(): kotlin.Unit {
+        }
+
+        public interface InterfaceWithJvmName {
+          /**
+           * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+           */
+          @kotlin.jvm.JvmSynthetic
+          public fun interfaceFunction(): kotlin.Unit
+
+          public companion object {
+            @get:kotlin.jvm.JvmSynthetic
+            @kotlin.jvm.JvmStatic
+            public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+            /**
+             * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+             */
+            @kotlin.jvm.JvmSynthetic
+            public fun staticFunction(): kotlin.Unit {
+            }
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Synthetics(
+    @get:JvmSynthetic val param: String,
+  ) {
+
+    @JvmSynthetic
+    val property: String? = null
+
+    @field:JvmSynthetic
+    val fieldProperty: String? = null
+
+    @get:JvmSynthetic
+    val propertyGet: String? = null
+
+    @set:JvmSynthetic
+    var propertySet: String? = null
+
+    @set:JvmSynthetic
+    @get:JvmSynthetic
+    var propertyGetAndSet: String? = null
+
+    @JvmSynthetic
+    fun function() {
+    }
+
+    // Interfaces can have JvmSynthetic, so covering a potential edge case of having a companion
+    // object with JvmSynthetic elements. Also covers an edge case where constants have getters
+    interface InterfaceWithJvmName {
+      @JvmSynthetic
+      fun interfaceFunction()
+
+      companion object {
+        @JvmStatic
+        @get:JvmSynthetic
+        val FOO_BOOL = false
+
+        @JvmSynthetic
+        @JvmStatic
+        fun staticFunction() {
+        }
+      }
+    }
+  }
+
+  @Test
+  fun throws() {
+    val typeSpec = Throwing::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Throwing @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) constructor() {
+        @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+        @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+        public var getterAndSetterThrows: kotlin.String? = null
+
+        @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+        public val getterThrows: kotlin.String? = null
+
+        @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+        public var setterThrows: kotlin.String? = null
+
+        @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+        public fun testFunction(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Throwing
+  @Throws(IllegalStateException::class)
+  constructor() {
+
+    @get:Throws(IllegalStateException::class)
+    val getterThrows: String? = null
+
+    @set:Throws(IllegalStateException::class)
+    var setterThrows: String? = null
+
+    @get:Throws(IllegalStateException::class)
+    @set:Throws(IllegalStateException::class)
+    var getterAndSetterThrows: String? = null
+
+    @Throws(IllegalStateException::class)
+    fun testFunction() {
+    }
+  }
+
+  // The meta-ist of metadata meta-tests.
+  @IgnoreForHandlerType(
+    reason = "Reflection can't parse non-runtime retained annotations",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun metaTest_elements() {
+    val typeSpec = Metadata::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @kotlin.SinceKotlin(version = "1.3")
+      @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME)
+      @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS))
+      public annotation class Metadata(
+        @get:kotlin.jvm.JvmName(name = "k")
+        public val kind: kotlin.Int = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "mv")
+        public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "bv")
+        public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "d1")
+        public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "d2")
+        public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "xs")
+        public val extraString: kotlin.String = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "pn")
+        public val packageName: kotlin.String = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "xi")
+        public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"),
+      )
+      """.trimIndent(),
+    )
+  }
+
+  // The meta-ist of metadata meta-tests.
+  @IgnoreForHandlerType(
+    reason = "Reflection can't parse non-runtime retained annotations",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun metaTest_reflection() {
+    val typeSpec = Metadata::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME)
+      @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS))
+      public annotation class Metadata(
+        @get:kotlin.jvm.JvmName(name = "k")
+        public val kind: kotlin.Int = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "mv")
+        public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "bv")
+        public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "d1")
+        public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "d2")
+        public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "xs")
+        public val extraString: kotlin.String = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "pn")
+        public val packageName: kotlin.String = throw NotImplementedError("Stub!"),
+        @get:kotlin.jvm.JvmName(name = "xi")
+        public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"),
+      )
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun classNamesAndNesting() {
+    // Make sure we parse class names correctly at all levels
+    val typeSpec = ClassNesting::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ClassNesting() {
+        public class NestedClass() {
+          public class SuperNestedClass() {
+            public inner class SuperDuperInnerClass()
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "compile-testing can't handle class names with dashes, will throw " +
+      "\"class file for com.squareup.kotlinpoet.metadata.specs.Fuzzy\$ClassNesting\$-Nested not found\"",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun classNamesAndNesting_pathological() {
+    // Make sure we parse class names correctly at all levels
+    val typeSpec = `Fuzzy$ClassNesting`::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class `Fuzzy${'$'}ClassNesting`() {
+        public class `-Nested`() {
+          public class SuperNestedClass() {
+            public inner class `-${'$'}Fuzzy${'$'}Super${'$'}Weird-Nested${'$'}Name`()
+          }
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Property site-target annotations are always stored on the synthetic annotations " +
+      "method, which is not accessible in the elements API",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun parameterAnnotations_reflective() {
+    val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ParameterAnnotations(
+        @property:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "${'$'}{'${'$'}'}a")
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b")
+        public val param1: kotlin.String,
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2")
+        param2: kotlin.String,
+      ) {
+        public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Property site-target annotations are always stored on the synthetic annotations " +
+      "method, which is not accessible in the elements API",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun parameterAnnotations_elements() {
+    val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class ParameterAnnotations(
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b")
+        public val param1: kotlin.String,
+        @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2")
+        param2: kotlin.String,
+      ) {
+        public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  annotation class CustomAnnotation(val name: String)
+
+  class ParameterAnnotations(
+    @property:CustomAnnotation("\$a")
+    @param:CustomAnnotation("b")
+    val param1: String,
+    @CustomAnnotation("2") param2: String,
+  ) {
+    fun function(@CustomAnnotation("woo") param1: String) {
+    }
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Non-runtime annotations are not present for reflection.",
+    handlerType = ELEMENTS,
+  )
+  @Test
+  fun classAnnotations_reflective() {
+    val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime")
+      public class ClassAnnotations()
+      """.trimIndent(),
+    )
+  }
+
+  @IgnoreForHandlerType(
+    reason = "Non-runtime annotations are not present for reflection.",
+    handlerType = REFLECTIVE,
+  )
+  @Test
+  fun classAnnotations_elements() {
+    val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BinaryCustomClassAnnotation(name = "Binary")
+      @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime")
+      public class ClassAnnotations()
+      """.trimIndent(),
+    )
+  }
+
+  @Retention(AnnotationRetention.SOURCE)
+  annotation class SourceCustomClassAnnotation(val name: String)
+
+  @Retention(AnnotationRetention.BINARY)
+  annotation class BinaryCustomClassAnnotation(val name: String)
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class RuntimeCustomClassAnnotation(val name: String)
+
+  @SourceCustomClassAnnotation("Source")
+  @BinaryCustomClassAnnotation("Binary")
+  @RuntimeCustomClassAnnotation("Runtime")
+  class ClassAnnotations
+
+  @Test
+  fun typeAnnotations() {
+    val typeSpec = TypeAnnotations::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class TypeAnnotations() {
+        public val foo: kotlin.collections.List<@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String> = throw NotImplementedError("Stub!")
+
+        public fun <T> bar(input: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String, input2: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation (@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.Int) -> @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  @Target(TYPE, TYPE_PARAMETER)
+  annotation class TypeAnnotation
+
+  class TypeAnnotations {
+    val foo: List<@TypeAnnotation String> = emptyList()
+
+    fun <@TypeAnnotation T> bar(
+      input: @TypeAnnotation String,
+      input2: @TypeAnnotation (@TypeAnnotation Int) -> @TypeAnnotation String,
+    ) {
+    }
+  }
+
+  // Regression test for https://github.com/square/kotlinpoet/issues/812
+  @Test
+  fun backwardTypeVarReferences() {
+    val typeSpec = Asset::class.toTypeSpecWithTestHandler()
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public class Asset<A : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>>() {
+        public fun <D : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<D>, C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>> function(): kotlin.Unit {
+        }
+
+        public class AssetIn<in C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetIn<C>>()
+
+        public class AssetOut<out B : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetOut<B>>()
+      }
+      """.trimIndent(),
+    )
+  }
+
+  class Asset<A : Asset<A>> {
+    fun <D : Asset<D>, C : Asset<A>> function() {
+    }
+
+    class AssetOut<out B : AssetOut<B>>
+    class AssetIn<in C : AssetIn<C>>
+  }
+
+  // Regression test for https://github.com/square/kotlinpoet/issues/821
+  @Test
+  fun abstractClass() {
+    val typeSpec = AbstractClass::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public abstract class AbstractClass() {
+        public val baz: kotlin.String? = null
+
+        public abstract val foo: kotlin.String
+
+        public abstract fun bar(): kotlin.Unit
+
+        public abstract fun barWithReturn(): kotlin.String
+
+        public fun fuz(): kotlin.Unit {
+        }
+
+        public fun fuzWithReturn(): kotlin.String = throw NotImplementedError("Stub!")
+      }
+      """.trimIndent(),
+    )
+  }
+
+  abstract class AbstractClass {
+    abstract val foo: String
+    abstract fun bar()
+    abstract fun barWithReturn(): String
+
+    val baz: String? = null
+    fun fuz() {}
+    fun fuzWithReturn(): String {
+      return ""
+    }
+  }
+
+  // Regression test for https://github.com/square/kotlinpoet/issues/820
+  @Test
+  fun internalAbstractProperty() {
+    val typeSpec = InternalAbstractPropertyHolder::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public abstract class InternalAbstractPropertyHolder() {
+        internal abstract val valProp: kotlin.String
+
+        internal abstract var varProp: kotlin.String
+      }
+      """.trimIndent(),
+    )
+  }
+
+  abstract class InternalAbstractPropertyHolder {
+    internal abstract val valProp: String
+    internal abstract var varProp: String
+  }
+
+  @Test
+  fun modalities() {
+    val abstractModalities = AbstractModalities::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(abstractModalities.trimmedToString()).isEqualTo(
+      """
+      public abstract class AbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface {
+        public val implicitFinalProp: kotlin.String? = null
+
+        public override val interfaceProp: kotlin.String? = null
+
+        public open val openProp: kotlin.String? = null
+
+        public fun implicitFinalFun(): kotlin.Unit {
+        }
+
+        public override fun interfaceFun(): kotlin.Unit {
+        }
+
+        public open fun openFun(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+
+    val finalAbstractModalities = FinalAbstractModalities::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(finalAbstractModalities.trimmedToString()).isEqualTo(
+      """
+      public abstract class FinalAbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface {
+        public final override val interfaceProp: kotlin.String? = null
+
+        public final override fun interfaceFun(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+
+    val modalities = Modalities::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(modalities.trimmedToString()).isEqualTo(
+      """
+      public class Modalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.AbstractModalities() {
+        public override val interfaceProp: kotlin.String? = null
+
+        public override val openProp: kotlin.String? = null
+
+        public override fun interfaceFun(): kotlin.Unit {
+        }
+
+        public override fun openFun(): kotlin.Unit {
+        }
+      }
+      """.trimIndent(),
+    )
+  }
+
+  interface ModalitiesInterface {
+    val interfaceProp: String?
+    fun interfaceFun()
+  }
+  abstract class AbstractModalities : ModalitiesInterface {
+    override val interfaceProp: String? = null
+    override fun interfaceFun() {
+    }
+    val implicitFinalProp: String? = null
+    fun implicitFinalFun() {
+    }
+    open val openProp: String? = null
+    open fun openFun() {
+    }
+  }
+  abstract class FinalAbstractModalities : ModalitiesInterface {
+    final override val interfaceProp: String? = null
+    final override fun interfaceFun() {
+    }
+  }
+  class Modalities : AbstractModalities() {
+    override val interfaceProp: String? = null
+    override fun interfaceFun() {
+    }
+
+    override val openProp: String? = null
+    override fun openFun() {
+    }
+  }
+
+  @Test
+  fun funClass() {
+    val funInterface = FunInterface::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(funInterface.trimmedToString()).isEqualTo(
+      """
+      public fun interface FunInterface {
+        public fun example(): kotlin.Unit
+      }
+      """.trimIndent(),
+    )
+  }
+
+  fun interface FunInterface {
+    fun example()
+  }
+
+  @Test
+  fun selfReferencingTypeParams() {
+    val typeSpec = Node::class.toTypeSpecWithTestHandler()
+
+    //language=kotlin
+    assertThat(typeSpec.trimmedToString()).isEqualTo(
+      """
+      public open class Node<T : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<T, R>, R : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<R, T>>() {
+        public var r: R? = null
+
+        public var t: T? = null
+      }
+      """.trimIndent(),
+    )
+  }
+
+  open class Node<T : Node<T, R>, R : Node<R, T>> {
+    var t: T? = null
+    var r: R? = null
+  }
+}
+
+class ClassNesting {
+  class NestedClass {
+    class SuperNestedClass {
+      inner class SuperDuperInnerClass
+    }
+  }
+}
+
+class `Fuzzy$ClassNesting` {
+  class `-Nested` {
+    class SuperNestedClass {
+      inner class `-$Fuzzy$Super$Weird-Nested$Name`
+    }
+  }
+}
+
+private fun TypeSpec.trimmedToString(): String {
+  return toString().trim()
+}
+
+inline class InlineClass(val value: String)
+
+@JvmInline
+value class ValueClass(val value: String)
+
+typealias TypeAliasName = String
+typealias GenericTypeAlias = List<String>
+typealias NestedTypeAlias = List<GenericTypeAlias>
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt
new file mode 100644
index 0000000..cb7c21d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ElementsClassInspector
+import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType
+import com.squareup.kotlinpoet.metadata.toKotlinClassMetadata
+import java.lang.annotation.Inherited
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.reflect.KClass
+import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.model.Statement
+
+/** Base test class that runs all tests with multiple [ClassInspectorTypes][ClassInspectorType]. */
+@RunWith(Parameterized::class)
+@KotlinPoetMetadataPreview
+abstract class MultiClassInspectorTest {
+  companion object {
+    @JvmStatic
+    @Parameterized.Parameters(name = "{0}")
+    fun data(): Collection<Array<ClassInspectorType>> {
+      return listOf(
+        arrayOf(ClassInspectorType.REFLECTIVE),
+        arrayOf(ClassInspectorType.ELEMENTS),
+      )
+    }
+  }
+
+  enum class ClassInspectorType {
+    NONE {
+      override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+        throw IllegalStateException("Should not be called, just here to default the jvmfield to something.")
+      }
+    },
+    REFLECTIVE {
+      override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+        return ReflectiveClassInspector.create()
+      }
+    },
+    ELEMENTS {
+      override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+        return ElementsClassInspector.create(testInstance.compilation.elements, testInstance.compilation.types)
+      }
+    },
+    ;
+
+    abstract fun create(testInstance: MultiClassInspectorTest): ClassInspector
+  }
+
+  @Retention(RUNTIME)
+  @Target(AnnotationTarget.FUNCTION)
+  @Inherited
+  annotation class IgnoreForHandlerType(
+    val reason: String,
+    val handlerType: ClassInspectorType,
+  )
+
+  @JvmField
+  @Parameter
+  var classInspectorType: ClassInspectorType = ClassInspectorType.NONE
+
+  @Rule
+  @JvmField
+  val compilation = CompilationRule()
+
+  @Rule
+  @JvmField
+  val ignoreForElementsRule = TestRule { base, description ->
+    object : Statement() {
+      override fun evaluate() {
+        val annotation = description.getAnnotation(
+          IgnoreForHandlerType::class.java,
+        )
+        val shouldIgnore = annotation?.handlerType == classInspectorType
+        Assume.assumeTrue(
+          "Ignoring ${description.methodName}: ${annotation?.reason}",
+          !shouldIgnore,
+        )
+        base.evaluate()
+      }
+    }
+  }
+
+  protected fun KClass<*>.toTypeSpecWithTestHandler(): TypeSpec {
+    return toTypeSpec(classInspectorType.create(this@MultiClassInspectorTest))
+  }
+
+  protected fun KClass<*>.toFileSpecWithTestHandler(): FileSpec {
+    val classInspector = classInspectorType.create(this@MultiClassInspectorTest)
+    return java.annotations.filterIsInstance<Metadata>().first().toKotlinClassMetadata<FileFacade>()
+      .toKmPackage()
+      .toFileSpec(classInspector, asClassName())
+  }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt
new file mode 100644
index 0000000..6347108
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+val prop: String = ""
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt
new file mode 100644
index 0000000..a4ecc3d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector
+import com.tschuchort.compiletesting.KotlinCompilation
+import com.tschuchort.compiletesting.SourceFile
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Test
+
+/**
+ * Class to test the new functionality of Issue#1036.
+ * @see <a href="https://github.com/square/kotlinpoet/issues/1036">issue</a>
+ * @author oberstrike
+ */
+@KotlinPoetMetadataPreview
+class ReflectiveClassInspectorTest {
+
+  data class Person(val name: String)
+
+  /**
+   * Tests if the [ReflectiveClassInspector] can be created without a
+   * custom ClassLoader and still works.
+   */
+  @Test
+  fun standardClassLoaderTest() {
+    val classInspector = ReflectiveClassInspector.create()
+    val className = Person::class.asClassName()
+    val declarationContainer = classInspector.declarationContainerFor(className)
+    assertNotNull(declarationContainer)
+  }
+
+  /**
+   * Tests if the [ReflectiveClassInspector] can be created with a
+   * custom ClassLoader.
+   */
+  @Test
+  fun useACustomClassLoaderTest() {
+    val testClass = "Person"
+    val testPropertyName = "name"
+    val testPropertyType = "String"
+    val testPackageName = "com.test"
+    val testClassName = ClassName(testPackageName, testClass)
+    val testKtFileName = "KClass.kt"
+
+    val kotlinSource = SourceFile.kotlin(
+      testKtFileName,
+      """
+            package $testPackageName
+            data class $testClass(val $testPropertyName: $testPropertyType)
+      """.trimIndent(),
+    )
+
+    val result = KotlinCompilation().apply {
+      sources = listOf(kotlinSource)
+    }.compile()
+
+    assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)
+    val classLoader = result.classLoader
+    val classInspector = ReflectiveClassInspector.create(classLoader)
+
+    val declarationContainer = classInspector.declarationContainerFor(testClassName)
+
+    val properties = declarationContainer.properties
+    assertEquals(1, properties.size)
+
+    val testProperty = properties.findLast { it.name == testPropertyName }
+    assertNotNull(testProperty)
+
+    val returnType = testProperty.returnType
+    assertNotNull(returnType)
+  }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt
new file mode 100644
index 0000000..4e1c268
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.metadata.specs.classinspectors
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import kotlin.test.Test
+
+@KotlinPoetMetadataPreview
+class ClassInspectorUtilTest {
+
+  @Test fun createClassName_simple() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Foo"))
+      .isEqualTo(ClassName("some.path", "Foo"))
+  }
+
+  @Test fun createClassName_nested() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Foo.Nested"))
+      .isEqualTo(ClassName("some.path", "Foo", "Nested"))
+  }
+
+  @Test fun createClassName_simple_dollarNameStart() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Foo$"))
+      .isEqualTo(ClassName("some.path", "Foo$"))
+  }
+
+  @Test fun createClassName_simple_dollarNameMiddle() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Fo${'$'}o"))
+      .isEqualTo(ClassName("some.path", "Fo${'$'}o"))
+  }
+
+  @Test fun createClassName_simple_dollarNameEnd() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo"))
+      .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo"))
+  }
+
+  @Test fun createClassName_nested_dollarNameStart() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Foo$"))
+      .isEqualTo(ClassName("some.path", "Nested", "Foo$"))
+  }
+
+  @Test fun createClassName_nested_dollarNameMiddle() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Fo${'$'}o"))
+      .isEqualTo(ClassName("some.path", "Nested", "Fo${'$'}o"))
+  }
+
+  @Test fun createClassName_nested_dollarNameEnd() {
+    assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo"))
+      .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo"))
+  }
+
+  @Test fun createClassName_noPackageName() {
+    assertThat(ClassInspectorUtil.createClassName("ClassWithNoPackage"))
+      .isEqualTo(ClassName("", "ClassWithNoPackage"))
+  }
+
+  // Regression test for avoiding https://github.com/square/kotlinpoet/issues/795
+  @Test fun createClassName_noEmptyNames() {
+    val noPackage = ClassInspectorUtil.createClassName("ClassWithNoPackage")
+    assertThat(noPackage.simpleNames.any { it.isEmpty() }).isFalse()
+
+    val withPackage = ClassInspectorUtil.createClassName("path/to/ClassWithNoPackage")
+    assertThat(withPackage.simpleNames.any { it.isEmpty() }).isFalse()
+  }
+
+  @Test fun createClassName_packageWithCaps() {
+    assertThat(ClassInspectorUtil.createClassName("some/Path/Foo.Nested"))
+      .isEqualTo(ClassName("some.Path", "Foo", "Nested"))
+  }
+
+  @Test fun throwsSpec_normal() {
+    assertThat(ClassInspectorUtil.createThrowsSpec(listOf(Exception::class.asClassName())))
+      .isEqualTo(
+        AnnotationSpec.builder(Throws::class.asClassName())
+          .addMember("exceptionClasses = [%T::class]", Exception::class.asClassName())
+          .build(),
+      )
+  }
+}
diff --git a/interop/ksp/api/ksp.api b/interop/ksp/api/ksp.api
new file mode 100644
index 0000000..cc8cb2b
--- /dev/null
+++ b/interop/ksp/api/ksp.api
@@ -0,0 +1,64 @@
+public final class com/squareup/kotlinpoet/ksp/AnnotationsKt {
+	public static final fun toAnnotationSpec (Lcom/google/devtools/ksp/symbol/KSAnnotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/ksp/KsClassDeclarationsKt {
+	public static final fun toClassName (Lcom/google/devtools/ksp/symbol/KSClassDeclaration;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ksp/KsTypesKt {
+	public static final fun toClassName (Lcom/google/devtools/ksp/symbol/KSType;)Lcom/squareup/kotlinpoet/ClassName;
+	public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSType;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSTypeArgument;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSTypeReference;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSType;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSTypeArgument;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSTypeReference;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun toTypeVariableName (Lcom/google/devtools/ksp/symbol/KSTypeParameter;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun toTypeVariableName$default (Lcom/google/devtools/ksp/symbol/KSTypeParameter;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/ksp/ModifiersKt {
+	public static final fun toKModifier (Lcom/google/devtools/ksp/symbol/Modifier;)Lcom/squareup/kotlinpoet/KModifier;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ksp/OriginatingKSFiles {
+	public abstract fun getFiles ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/ksp/OriginatingKSFilesKt {
+	public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/PropertySpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun kspDependencies (Lcom/squareup/kotlinpoet/FileSpec;ZLjava/lang/Iterable;)Lcom/google/devtools/ksp/processing/Dependencies;
+	public static synthetic fun kspDependencies$default (Lcom/squareup/kotlinpoet/FileSpec;ZLjava/lang/Iterable;ILjava/lang/Object;)Lcom/google/devtools/ksp/processing/Dependencies;
+	public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/FileSpec;)Ljava/util/List;
+	public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/FunSpec;)Ljava/util/List;
+	public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/PropertySpec;)Ljava/util/List;
+	public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/TypeAliasSpec;)Ljava/util/List;
+	public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/TypeSpec;)Ljava/util/List;
+	public static final fun writeTo (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/Dependencies;)V
+	public static final fun writeTo (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;ZLjava/lang/Iterable;)V
+	public static synthetic fun writeTo$default (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;ZLjava/lang/Iterable;ILjava/lang/Object;)V
+}
+
+public abstract interface class com/squareup/kotlinpoet/ksp/TypeParameterResolver {
+	public static final field Companion Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver$Companion;
+	public abstract fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public abstract fun getParametersMap ()Ljava/util/Map;
+}
+
+public final class com/squareup/kotlinpoet/ksp/TypeParameterResolver$Companion {
+	public final fun getEMPTY ()Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+}
+
+public final class com/squareup/kotlinpoet/ksp/TypeParameterResolverKt {
+	public static final fun toTypeParameterResolver (Ljava/util/List;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;Ljava/lang/String;)Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+	public static synthetic fun toTypeParameterResolver$default (Ljava/util/List;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+}
+
+public final class com/squareup/kotlinpoet/ksp/VisibilitiesKt {
+	public static final fun toKModifier (Lcom/google/devtools/ksp/symbol/Visibility;)Lcom/squareup/kotlinpoet/KModifier;
+}
+
diff --git a/interop/ksp/build.gradle.kts b/interop/ksp/build.gradle.kts
new file mode 100644
index 0000000..c18fa7c
--- /dev/null
+++ b/interop/ksp/build.gradle.kts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+tasks.jar {
+  manifest {
+    attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.ksp")
+  }
+}
+
+dependencies {
+  api(project(":kotlinpoet"))
+  compileOnly(libs.ksp.api)
+  testImplementation(libs.kotlin.junit)
+  testImplementation(libs.truth)
+}
diff --git a/interop/ksp/gradle.properties b/interop/ksp/gradle.properties
new file mode 100644
index 0000000..dda5a7d
--- /dev/null
+++ b/interop/ksp/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-ksp
+POM_NAME=KotlinPoet (KSP Interop)
+POM_DESCRIPTION=Extensions for interop with KSP (Kotlin Symbol Processing).
+POM_PACKAGING=jar
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt
new file mode 100644
index 0000000..7964de2
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.ParameterizedTypeName
+
+/** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
+public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
+  val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
+    is ClassName -> AnnotationSpec.builder(type)
+    is ParameterizedTypeName -> AnnotationSpec.builder(type)
+    else -> error("This is never possible.")
+  }
+  useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
+  // TODO support type params once they're exposed https://github.com/google/ksp/issues/753
+  for (argument in arguments) {
+    val member = CodeBlock.builder()
+    val name = argument.name!!.getShortName()
+    member.add("%N = ", name)
+    addValueToBlock(argument.value!!, member)
+    builder.addMember(member.build())
+  }
+  return builder.build()
+}
+
+private val AnnotationUseSiteTarget.kpAnalog: UseSiteTarget get() = when (this) {
+  AnnotationUseSiteTarget.FILE -> UseSiteTarget.FILE
+  AnnotationUseSiteTarget.PROPERTY -> UseSiteTarget.PROPERTY
+  AnnotationUseSiteTarget.FIELD -> UseSiteTarget.FIELD
+  AnnotationUseSiteTarget.GET -> UseSiteTarget.GET
+  AnnotationUseSiteTarget.SET -> UseSiteTarget.SET
+  AnnotationUseSiteTarget.RECEIVER -> UseSiteTarget.RECEIVER
+  AnnotationUseSiteTarget.PARAM -> UseSiteTarget.PARAM
+  AnnotationUseSiteTarget.SETPARAM -> UseSiteTarget.SETPARAM
+  AnnotationUseSiteTarget.DELEGATE -> UseSiteTarget.DELEGATE
+}
+
+internal fun KSType.unwrapTypeAlias(): KSType {
+  return if (this.declaration is KSTypeAlias) {
+    (this.declaration as KSTypeAlias).type.resolve()
+  } else {
+    this
+  }
+}
+
+private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
+  when (value) {
+    is List<*> -> {
+      // Array type
+      val arrayType = when (value.firstOrNull()) {
+        is Boolean -> "booleanArrayOf"
+        is Byte -> "byteArrayOf"
+        is Char -> "charArrayOf"
+        is Short -> "shortArrayOf"
+        is Int -> "intArrayOf"
+        is Long -> "longArrayOf"
+        is Float -> "floatArrayOf"
+        is Double -> "doubleArrayOf"
+        else -> "arrayOf"
+      }
+      member.add("$arrayType(⇥⇥")
+      value.forEachIndexed { index, innerValue ->
+        if (index > 0) member.add(", ")
+        addValueToBlock(innerValue!!, member)
+      }
+      member.add("⇤⇤)")
+    }
+    is KSType -> {
+      val unwrapped = value.unwrapTypeAlias()
+      val isEnum = (unwrapped.declaration as KSClassDeclaration).classKind == ClassKind.ENUM_ENTRY
+      if (isEnum) {
+        val parent = unwrapped.declaration.parentDeclaration as KSClassDeclaration
+        val entry = unwrapped.declaration.simpleName.getShortName()
+        member.add("%T.%L", parent.toClassName(), entry)
+      } else {
+        member.add("%T::class", unwrapped.toClassName())
+      }
+    }
+    is KSName ->
+      member.add(
+        "%T.%L",
+        ClassName.bestGuess(value.getQualifier()),
+        value.getShortName(),
+      )
+    is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
+    else -> member.add(memberForValue(value))
+  }
+}
+
+/**
+ * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
+ * Handles a number of special cases, such as appending "f" to `Float` values, and uses
+ * `%L` for other types.
+ */
+internal fun memberForValue(value: Any) = when (value) {
+  is Class<*> -> CodeBlock.of("%T::class", value)
+  is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
+  is String -> CodeBlock.of("%S", value)
+  is Float -> CodeBlock.of("%Lf", value)
+  is Double -> CodeBlock.of("%L", value)
+  is Char -> CodeBlock.of("'%L'", value)
+  is Byte -> CodeBlock.of("$value.toByte()")
+  is Short -> CodeBlock.of("$value.toShort()")
+  // Int or Boolean
+  else -> CodeBlock.of("%L", value)
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt
new file mode 100644
index 0000000..443be1b
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.squareup.kotlinpoet.ClassName
+
+/** Returns the [ClassName] representation of this [KSClassDeclaration]. */
+public fun KSClassDeclaration.toClassName(): ClassName {
+  return toClassNameInternal()
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt
new file mode 100644
index 0000000..0572b73
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.google.devtools.ksp.symbol.Variance.CONTRAVARIANT
+import com.google.devtools.ksp.symbol.Variance.COVARIANT
+import com.google.devtools.ksp.symbol.Variance.INVARIANT
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+
+/** Returns the [ClassName] representation of this [KSType] IFF it's a [KSClassDeclaration]. */
+public fun KSType.toClassName(): ClassName {
+  val decl = declaration
+  check(decl is KSClassDeclaration) {
+    "Declaration was not a KSClassDeclaration: $this"
+  }
+  return decl.toClassName()
+}
+
+/**
+ * Returns the [TypeName] representation of this [KSType].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ *                          declarations can be anything with generics that child nodes declare as
+ *                          defined by [KSType.arguments].
+ */
+public fun KSType.toTypeName(
+  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName = toTypeName(typeParamResolver, emptyList())
+
+internal fun KSType.toTypeName(
+  typeParamResolver: TypeParameterResolver,
+  typeArguments: List<KSTypeArgument>,
+): TypeName {
+  require(!isError) {
+    "Error type '$this' is not resolvable in the current round of processing."
+  }
+  val type = when (val decl = declaration) {
+    is KSClassDeclaration -> {
+      val arguments = if (decl.classKind == ClassKind.ANNOTATION_CLASS) {
+        arguments
+      } else {
+        typeArguments
+      }
+
+      decl.toClassName().withTypeArguments(arguments.map { it.toTypeName(typeParamResolver) })
+    }
+    is KSTypeParameter -> typeParamResolver[decl.name.getShortName()]
+    is KSTypeAlias -> {
+      var typeAlias: KSTypeAlias = decl
+      var arguments = arguments
+
+      var resolvedType: KSType
+      var mappedArgs: List<KSTypeArgument>
+      var extraResolver: TypeParameterResolver = typeParamResolver
+      while (true) {
+        resolvedType = typeAlias.type.resolve()
+        mappedArgs = mapTypeArgumentsFromTypeAliasToAbbreviatedType(
+          typeAlias = typeAlias,
+          typeAliasTypeArguments = arguments,
+          abbreviatedType = resolvedType,
+        )
+        extraResolver = if (typeAlias.typeParameters.isEmpty()) {
+          extraResolver
+        } else {
+          typeAlias.typeParameters.toTypeParameterResolver(extraResolver)
+        }
+
+        typeAlias = resolvedType.declaration as? KSTypeAlias ?: break
+        arguments = mappedArgs
+      }
+
+      val abbreviatedType = resolvedType
+        .toTypeName(extraResolver)
+        .copy(nullable = isMarkedNullable)
+        .rawType()
+        .withTypeArguments(mappedArgs.map { it.toTypeName(extraResolver) })
+
+      val aliasArgs = typeArguments.map { it.toTypeName(typeParamResolver) }
+
+      decl.toClassNameInternal()
+        .withTypeArguments(aliasArgs)
+        .copy(tags = mapOf(TypeAliasTag::class to TypeAliasTag(abbreviatedType)))
+    }
+    else -> error("Unsupported type: $declaration")
+  }
+
+  return type.copy(nullable = isMarkedNullable)
+}
+
+private fun mapTypeArgumentsFromTypeAliasToAbbreviatedType(
+  typeAlias: KSTypeAlias,
+  typeAliasTypeArguments: List<KSTypeArgument>,
+  abbreviatedType: KSType,
+): List<KSTypeArgument> {
+  return abbreviatedType.arguments
+    .map { typeArgument ->
+      // Check if type argument is a reference to a typealias type parameter, and not an actual type.
+      val typeAliasTypeParameterIndex = typeAlias.typeParameters.indexOfFirst { typeAliasTypeParameter ->
+        typeAliasTypeParameter.name.asString() == typeArgument.type.toString()
+      }
+      if (typeAliasTypeParameterIndex >= 0) {
+        typeAliasTypeArguments[typeAliasTypeParameterIndex]
+      } else {
+        typeArgument
+      }
+    }
+}
+
+/**
+ * Returns a [TypeVariableName] representation of this [KSTypeParameter].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ *                          declarations can be anything with generics that child nodes declare as
+ *                          defined by [KSType.arguments].
+ */
+public fun KSTypeParameter.toTypeVariableName(
+  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeVariableName {
+  val typeVarName = name.getShortName()
+  val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver) }.toList()
+  val typeVarVariance = when (variance) {
+    COVARIANT -> KModifier.OUT
+    CONTRAVARIANT -> KModifier.IN
+    else -> null
+  }
+  return TypeVariableName(typeVarName, bounds = typeVarBounds, variance = typeVarVariance)
+}
+
+/**
+ * Returns a [TypeName] representation of this [KSTypeArgument].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ *                          declarations can be anything with generics that child nodes declare as
+ *                          defined by [KSType.arguments].
+ */
+public fun KSTypeArgument.toTypeName(
+  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName {
+  val type = this.type ?: return STAR
+  return when (variance) {
+    COVARIANT -> WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
+    CONTRAVARIANT -> WildcardTypeName.consumerOf(type.toTypeName(typeParamResolver))
+    Variance.STAR -> STAR
+    INVARIANT -> type.toTypeName(typeParamResolver)
+  }
+}
+
+/**
+ * Returns a [TypeName] representation of this [KSTypeReference].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ *                          declarations can be anything with generics that child nodes declare as
+ *                          defined by [KSType.arguments].
+ */
+public fun KSTypeReference.toTypeName(
+  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName {
+  return resolve().toTypeName(typeParamResolver, element?.typeArguments.orEmpty())
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt
new file mode 100644
index 0000000..d4d8aa5
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.Modifier
+import com.google.devtools.ksp.symbol.Modifier.ABSTRACT
+import com.google.devtools.ksp.symbol.Modifier.ACTUAL
+import com.google.devtools.ksp.symbol.Modifier.ANNOTATION
+import com.google.devtools.ksp.symbol.Modifier.CROSSINLINE
+import com.google.devtools.ksp.symbol.Modifier.DATA
+import com.google.devtools.ksp.symbol.Modifier.ENUM
+import com.google.devtools.ksp.symbol.Modifier.EXPECT
+import com.google.devtools.ksp.symbol.Modifier.EXTERNAL
+import com.google.devtools.ksp.symbol.Modifier.FINAL
+import com.google.devtools.ksp.symbol.Modifier.FUN
+import com.google.devtools.ksp.symbol.Modifier.IN
+import com.google.devtools.ksp.symbol.Modifier.INFIX
+import com.google.devtools.ksp.symbol.Modifier.INLINE
+import com.google.devtools.ksp.symbol.Modifier.INNER
+import com.google.devtools.ksp.symbol.Modifier.INTERNAL
+import com.google.devtools.ksp.symbol.Modifier.LATEINIT
+import com.google.devtools.ksp.symbol.Modifier.NOINLINE
+import com.google.devtools.ksp.symbol.Modifier.OPEN
+import com.google.devtools.ksp.symbol.Modifier.OPERATOR
+import com.google.devtools.ksp.symbol.Modifier.OUT
+import com.google.devtools.ksp.symbol.Modifier.OVERRIDE
+import com.google.devtools.ksp.symbol.Modifier.PRIVATE
+import com.google.devtools.ksp.symbol.Modifier.PROTECTED
+import com.google.devtools.ksp.symbol.Modifier.PUBLIC
+import com.google.devtools.ksp.symbol.Modifier.REIFIED
+import com.google.devtools.ksp.symbol.Modifier.SEALED
+import com.google.devtools.ksp.symbol.Modifier.SUSPEND
+import com.google.devtools.ksp.symbol.Modifier.TAILREC
+import com.google.devtools.ksp.symbol.Modifier.VALUE
+import com.google.devtools.ksp.symbol.Modifier.VARARG
+import com.squareup.kotlinpoet.KModifier
+
+/**
+ * Returns the [KModifier] representation of this [Modifier] or null if this is a Java-only
+ * modifier (i.e. prefixed with `JAVA_`), which do not have obvious [KModifier] analogues.
+ */
+public fun Modifier.toKModifier(): KModifier? {
+  return when (this) {
+    PUBLIC -> KModifier.PUBLIC
+    PRIVATE -> KModifier.PRIVATE
+    INTERNAL -> KModifier.INTERNAL
+    PROTECTED -> KModifier.PROTECTED
+    IN -> KModifier.IN
+    OUT -> KModifier.OUT
+    OVERRIDE -> KModifier.OVERRIDE
+    LATEINIT -> KModifier.LATEINIT
+    ENUM -> KModifier.ENUM
+    SEALED -> KModifier.SEALED
+    ANNOTATION -> KModifier.ANNOTATION
+    DATA -> KModifier.DATA
+    INNER -> KModifier.INNER
+    FUN -> KModifier.FUN
+    VALUE -> KModifier.VALUE
+    SUSPEND -> KModifier.SUSPEND
+    TAILREC -> KModifier.TAILREC
+    OPERATOR -> KModifier.OPERATOR
+    INFIX -> KModifier.INFIX
+    INLINE -> KModifier.INLINE
+    EXTERNAL -> KModifier.EXTERNAL
+    ABSTRACT -> KModifier.ABSTRACT
+    FINAL -> KModifier.FINAL
+    OPEN -> KModifier.OPEN
+    VARARG -> KModifier.VARARG
+    NOINLINE -> KModifier.NOINLINE
+    CROSSINLINE -> KModifier.CROSSINLINE
+    REIFIED -> KModifier.REIFIED
+    EXPECT -> KModifier.EXPECT
+    ACTUAL -> KModifier.ACTUAL
+    else -> null
+  }
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt
new file mode 100644
index 0000000..f7c40b3
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.symbol.KSFile
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.Taggable
+import com.squareup.kotlinpoet.TypeAliasSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.tag
+import java.io.OutputStreamWriter
+import java.nio.charset.StandardCharsets
+
+/**
+ * A simple holder class for containing originating [KSFiles][KSFile], which are used by KSP to
+ * inform its incremental processing.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ */
+public interface OriginatingKSFiles {
+  public val files: List<KSFile>
+}
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun TypeSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun FunSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun PropertySpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun TypeAliasSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/**
+ * Returns the list of all files added to the contained
+ * [TypeSpecs][TypeSpec], [PropertySpecs][PropertySpec], [FunSpecs][FunSpec], or
+ * [TypeAliasSpecs][TypeAliasSpec] contained in this spec.
+ */
+public fun FileSpec.originatingKSFiles(): List<KSFile> {
+  return members
+    .flatMap {
+      when (it) {
+        is FunSpec -> it.originatingKSFiles()
+        is PropertySpec -> it.originatingKSFiles()
+        is TypeSpec -> it.originatingKSFiles()
+        is TypeAliasSpec -> it.originatingKSFiles()
+        else -> emptyList()
+      }
+    }
+    .distinct()
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun TypeAliasSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeAliasSpec.Builder = apply {
+  getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun PropertySpec.Builder.addOriginatingKSFile(ksFile: KSFile): PropertySpec.Builder = apply {
+  getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun FunSpec.Builder.addOriginatingKSFile(ksFile: KSFile): FunSpec.Builder = apply {
+  getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun TypeSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeSpec.Builder = apply {
+  getOrCreateKSFilesTag().add(ksFile)
+}
+
+/**
+ * Writes this [FileSpec] to a given [codeGenerator] with the given [originatingKSFiles].
+ *
+ * Note that if none are specified, the [originatingKSFiles] argument defaults to using
+ * [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
+ * contained declarations.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @param codeGenerator the [CodeGenerator] to write to.
+ * @param aggregating flag indicating if this is an aggregating symbol processor.
+ */
+public fun FileSpec.writeTo(
+  codeGenerator: CodeGenerator,
+  aggregating: Boolean,
+  originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
+) {
+  val dependencies = kspDependencies(aggregating, originatingKSFiles)
+  writeTo(codeGenerator, dependencies)
+}
+
+/**
+ * Writes this [FileSpec] to a given [codeGenerator] with the given [dependencies].
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @see kspDependencies
+ * @param codeGenerator the [CodeGenerator] to write to.
+ * @param dependencies the [Dependencies] to create a new file with.
+ */
+public fun FileSpec.writeTo(
+  codeGenerator: CodeGenerator,
+  dependencies: Dependencies,
+) {
+  val file = codeGenerator.createNewFile(dependencies, packageName, name)
+  // Don't use writeTo(file) because that tries to handle directories under the hood
+  OutputStreamWriter(file, StandardCharsets.UTF_8)
+    .use(::writeTo)
+}
+
+/**
+ * Returns a KSP [Dependencies] component of this [FileSpec] with the given [originatingKSFiles],
+ * intended to be used in tandem with [writeTo].
+ *
+ * Note that if no [originatingKSFiles] are specified, the [originatingKSFiles] argument defaults
+ * to using [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
+ * contained declarations.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @see FileSpec.writeTo
+ * @param aggregating flag indicating if this is an aggregating symbol processor.
+ */
+public fun FileSpec.kspDependencies(
+  aggregating: Boolean,
+  originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
+): Dependencies = Dependencies(aggregating, *originatingKSFiles.toList().toTypedArray())
+
+/**
+ * A mutable [OriginatingKSFiles] instance for use with KotlinPoet Builders via [Taggable.Builder].
+ */
+private interface MutableOriginatingKSFiles : OriginatingKSFiles {
+  override val files: MutableList<KSFile>
+}
+
+private data class MutableOriginatingKSFilesImpl(override val files: MutableList<KSFile> = mutableListOf()) : MutableOriginatingKSFiles
+
+private fun Taggable.getKSFilesTag(): List<KSFile> {
+  return tag<OriginatingKSFiles>()?.files.orEmpty()
+}
+
+private fun Taggable.Builder<*>.getOrCreateKSFilesTag(): MutableList<KSFile> {
+  val holder = tags.getOrPut(
+    OriginatingKSFiles::class,
+    ::MutableOriginatingKSFilesImpl,
+  ) as MutableOriginatingKSFiles
+  return holder.files
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt
new file mode 100644
index 0000000..1304b2a
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.squareup.kotlinpoet.TypeVariableName
+
+/**
+ * A resolver for enclosing declarations' type parameters. Parent declarations can be anything with
+ * generics that child nodes declare as defined by [KSType.arguments].
+ *
+ * This is important for resolving inherited generics on child declarations, as KSP interop
+ * otherwise can't resolve them.
+ *
+ * In general, you want to retrieve an instance of this via [toTypeParameterResolver].
+ *
+ * @see toTypeParameterResolver
+ */
+public interface TypeParameterResolver {
+  public val parametersMap: Map<String, TypeVariableName>
+  public operator fun get(index: String): TypeVariableName
+
+  public companion object {
+    /**
+     * An empty instance of [TypeParameterResolver], only should be used if enclosing declarations
+     * are known to not have arguments, such as top-level classes.
+     */
+    public val EMPTY: TypeParameterResolver = object : TypeParameterResolver {
+      override val parametersMap: Map<String, TypeVariableName> = emptyMap()
+
+      override fun get(index: String): TypeVariableName = throw NoSuchElementException("No TypeParameter found for index $index")
+    }
+  }
+}
+
+/**
+ * Returns a [TypeParameterResolver] for this list of [KSTypeParameters][KSTypeParameter] for use
+ * with enclosed declarations.
+ *
+ * @param parent the optional parent resolver, if any. An example of this is cases where you might
+ *               create a resolver for a [KSFunction] and supply a parent resolved from the
+ *               enclosing [KSClassDeclaration].
+ * @param sourceTypeHint an optional hint for error messages. Unresolvable parameter IDs will
+ *                       include this hint in the thrown error's message.
+ */
+public fun List<KSTypeParameter>.toTypeParameterResolver(
+  parent: TypeParameterResolver? = null,
+  sourceTypeHint: String = "<unknown>",
+): TypeParameterResolver {
+  val parametersMap = LinkedHashMap<String, TypeVariableName>()
+  val typeParamResolver = { id: String ->
+    parametersMap[id]
+      ?: parent?.get(id)
+      ?: throw IllegalStateException(
+        "No type argument found for $id! Analyzed $sourceTypeHint with known parameters " +
+          "${parametersMap.keys}",
+      )
+  }
+
+  val resolver = object : TypeParameterResolver {
+    override val parametersMap: Map<String, TypeVariableName> = parametersMap
+
+    override operator fun get(index: String): TypeVariableName = typeParamResolver(index)
+  }
+
+  // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
+  for (typeVar in this) {
+    // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
+    // replacement later that may add bounds referencing this.
+    val id = typeVar.name.getShortName()
+    parametersMap[id] = TypeVariableName(id)
+  }
+
+  for (typeVar in this) {
+    val id = typeVar.name.getShortName()
+    // Now replace it with the full version.
+    parametersMap[id] = typeVar.toTypeVariableName(resolver)
+  }
+
+  return resolver
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt
new file mode 100644
index 0000000..dcca6d2
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.Visibility
+import com.google.devtools.ksp.symbol.Visibility.JAVA_PACKAGE
+import com.google.devtools.ksp.symbol.Visibility.LOCAL
+import com.squareup.kotlinpoet.KModifier
+
+/**
+ * Returns the [KModifier] representation of this visibility or null if this is [JAVA_PACKAGE]
+ * or [LOCAL] (which do not have obvious [KModifier] alternatives).
+ */
+public fun Visibility.toKModifier(): KModifier? {
+  return when (this) {
+    Visibility.PUBLIC -> KModifier.PUBLIC
+    Visibility.PRIVATE -> KModifier.PRIVATE
+    Visibility.PROTECTED -> KModifier.PROTECTED
+    Visibility.INTERNAL -> KModifier.INTERNAL
+    else -> null
+  }
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt
new file mode 100644
index 0000000..7bc8fed
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.isLocal
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.TypeName
+
+internal fun TypeName.rawType(): ClassName {
+  return findRawType() ?: throw IllegalArgumentException("Cannot get raw type from $this")
+}
+
+internal fun TypeName.findRawType(): ClassName? {
+  return when (this) {
+    is ClassName -> this
+    is ParameterizedTypeName -> rawType
+    is LambdaTypeName -> {
+      var count = parameters.size
+      if (receiver != null) {
+        count++
+      }
+      val functionSimpleName = if (count >= 23) {
+        "FunctionN"
+      } else {
+        "Function$count"
+      }
+      ClassName("kotlin.jvm.functions", functionSimpleName)
+    }
+    else -> null
+  }
+}
+
+internal fun ClassName.withTypeArguments(arguments: List<TypeName>): TypeName {
+  return if (arguments.isEmpty()) {
+    this
+  } else {
+    this.parameterizedBy(arguments)
+  }
+}
+
+internal fun KSDeclaration.toClassNameInternal(): ClassName {
+  require(!isLocal()) {
+    "Local/anonymous classes are not supported!"
+  }
+  val pkgName = packageName.asString()
+  val typesString = checkNotNull(qualifiedName).asString().removePrefix("$pkgName.")
+
+  val simpleNames = typesString
+    .split(".")
+  return ClassName(pkgName, simpleNames)
+}
diff --git a/interop/ksp/test-processor/build.gradle.kts b/interop/ksp/test-processor/build.gradle.kts
new file mode 100644
index 0000000..9fc212e
--- /dev/null
+++ b/interop/ksp/test-processor/build.gradle.kts
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+plugins {
+  id("com.google.devtools.ksp")
+}
+
+dependencies {
+  implementation(project(":kotlinpoet"))
+  implementation(project(":interop:ksp"))
+  implementation(libs.autoService)
+  compileOnly(libs.ksp.api)
+  ksp(libs.autoService.ksp)
+  // Always force the latest version of the KSP/kotlin impl in tests to match what we're building against
+  testImplementation(libs.ksp.api)
+  testImplementation(libs.kotlin.compilerEmbeddable)
+  testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+  testImplementation(libs.ksp)
+  testImplementation(libs.kotlinCompileTesting)
+  testImplementation(libs.kotlinCompileTesting.ksp)
+  testImplementation(libs.kotlin.junit)
+  testImplementation(libs.truth)
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt
new file mode 100644
index 0000000..b1d41d4
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.devtools.ksp.getDeclaredFunctions
+import com.google.devtools.ksp.getDeclaredProperties
+import com.google.devtools.ksp.getVisibility
+import com.google.devtools.ksp.isConstructor
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
+import com.squareup.kotlinpoet.ksp.kspDependencies
+import com.squareup.kotlinpoet.ksp.originatingKSFiles
+import com.squareup.kotlinpoet.ksp.toAnnotationSpec
+import com.squareup.kotlinpoet.ksp.toKModifier
+import com.squareup.kotlinpoet.ksp.toTypeName
+import com.squareup.kotlinpoet.ksp.toTypeParameterResolver
+import com.squareup.kotlinpoet.ksp.toTypeVariableName
+import com.squareup.kotlinpoet.ksp.writeTo
+
+/**
+ * A simple processor that generates a skeleton API of classes annotated with [ExampleAnnotation]
+ * for test and verification purposes.
+ */
+class TestProcessor(private val env: SymbolProcessorEnvironment) : SymbolProcessor {
+
+  private val unwrapTypeAliases = env.options["unwrapTypeAliases"]?.toBooleanStrictOrNull() ?: false
+
+  override fun process(resolver: Resolver): List<KSAnnotated> {
+    resolver.getSymbolsWithAnnotation(ExampleAnnotation::class.java.canonicalName)
+      .forEach(::process)
+    return emptyList()
+  }
+
+  private fun process(decl: KSAnnotated) {
+    check(decl is KSClassDeclaration)
+
+    val classBuilder = TypeSpec.classBuilder(decl.simpleName.getShortName())
+      .addOriginatingKSFile(decl.containingFile!!)
+      .apply {
+        decl.getVisibility().toKModifier()?.let { addModifiers(it) }
+        addModifiers(decl.modifiers.mapNotNull { it.toKModifier() })
+        addAnnotations(
+          decl.annotations
+            .filterNot { it.shortName.getShortName() == "ExampleAnnotation" }
+            .map { it.toAnnotationSpec() }.asIterable(),
+        )
+      }
+    val classTypeParams = decl.typeParameters.toTypeParameterResolver()
+    classBuilder.addTypeVariables(
+      decl.typeParameters.map { typeParam ->
+        typeParam.toTypeVariableName(classTypeParams).let {
+          if (unwrapTypeAliases) {
+            it.unwrapTypeAlias()
+          } else {
+            it
+          }
+        }
+      },
+    )
+
+    // Add properties
+    for (property in decl.getDeclaredProperties()) {
+      classBuilder.addProperty(
+        PropertySpec.builder(
+          property.simpleName.getShortName(),
+          property.type.toTypeName(classTypeParams).let {
+            if (unwrapTypeAliases) {
+              it.unwrapTypeAlias()
+            } else {
+              it
+            }
+          },
+        )
+          .addOriginatingKSFile(decl.containingFile!!)
+          .mutable(property.isMutable)
+          .apply {
+            property.getVisibility().toKModifier()?.let { addModifiers(it) }
+            addModifiers(property.modifiers.mapNotNull { it.toKModifier() })
+            addAnnotations(
+              property.annotations
+                .map { it.toAnnotationSpec() }.asIterable(),
+            )
+          }
+          .build(),
+      )
+    }
+
+    // Add functions
+    for (function in decl.getDeclaredFunctions().filterNot { it.isConstructor() }) {
+      val functionTypeParams = function.typeParameters.toTypeParameterResolver(classTypeParams)
+      classBuilder.addFunction(
+        FunSpec.builder(function.simpleName.getShortName())
+          .addOriginatingKSFile(decl.containingFile!!)
+          .apply {
+            function.getVisibility().toKModifier()?.let { addModifiers(it) }
+            addModifiers(function.modifiers.mapNotNull { it.toKModifier() })
+          }
+          .addTypeVariables(
+            function.typeParameters.map { typeParam ->
+              typeParam.toTypeVariableName(functionTypeParams).let {
+                if (unwrapTypeAliases) {
+                  it.unwrapTypeAlias()
+                } else {
+                  it
+                }
+              }
+            },
+          )
+          .addParameters(
+            function.parameters.map { parameter ->
+              val parameterType = parameter.type.toTypeName(functionTypeParams).let {
+                if (unwrapTypeAliases) {
+                  it.unwrapTypeAlias()
+                } else {
+                  it
+                }
+              }
+              parameter.name?.let {
+                ParameterSpec.builder(it.getShortName(), parameterType).build()
+              } ?: ParameterSpec.unnamed(parameterType)
+            },
+          )
+          .returns(
+            function.returnType!!.toTypeName(functionTypeParams).let {
+              if (unwrapTypeAliases) {
+                it.unwrapTypeAlias()
+              } else {
+                it
+              }
+            },
+          )
+          .build(),
+      )
+    }
+
+    val typeSpec = classBuilder.build()
+    val fileSpec = FileSpec.builder(decl.packageName.asString(), "Test${typeSpec.name}")
+      .addType(typeSpec)
+      .build()
+
+    // Ensure that we're properly de-duping these under the hood.
+    check(fileSpec.originatingKSFiles().size == 1)
+
+    val dependencies = fileSpec.kspDependencies(aggregating = true)
+    check(dependencies.originatingFiles.size == 1)
+    check(dependencies.originatingFiles[0] == decl.containingFile)
+
+    fileSpec.writeTo(env.codeGenerator, dependencies)
+  }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt
new file mode 100644
index 0000000..35bf5ce
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+
+@AutoService(SymbolProcessorProvider::class)
+class TestProcessorProvider : SymbolProcessorProvider {
+  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+    return TestProcessor(environment)
+  }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt
new file mode 100644
index 0000000..599f8d7
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.tag
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import java.util.TreeSet
+
+/*
+ * Example implementation of how to unwrap a typealias from TypeNameAliasTag
+ */
+
+internal fun TypeName.unwrapTypeAliasReal(): TypeName {
+  return tag<TypeAliasTag>()?.abbreviatedType?.let { unwrappedType ->
+    // If any type is nullable, then the whole thing is nullable
+    var isAnyNullable = isNullable
+    // Keep track of all annotations across type levels. Sort them too for consistency.
+    val runningAnnotations = TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
+      addAll(annotations)
+    }
+    val nestedUnwrappedType = unwrappedType.unwrapTypeAlias()
+    runningAnnotations.addAll(nestedUnwrappedType.annotations)
+    isAnyNullable = isAnyNullable || nestedUnwrappedType.isNullable
+    nestedUnwrappedType.copy(nullable = isAnyNullable, annotations = runningAnnotations.toList())
+  } ?: this
+}
+
+// TypeVariableName gets a special overload because these usually need to be kept in a type-safe
+// manner.
+internal fun TypeVariableName.unwrapTypeAlias(): TypeVariableName {
+  return TypeVariableName(
+    name = name,
+    bounds = bounds.map { it.unwrapTypeAlias() },
+    variance = variance,
+  )
+    .copy(nullable = isNullable, annotations = annotations, tags = tags)
+}
+
+internal fun TypeName.unwrapTypeAlias(): TypeName {
+  return when (this) {
+    is ClassName -> unwrapTypeAliasReal()
+    is ParameterizedTypeName -> unwrapTypeAliasReal()
+    is TypeVariableName -> unwrapTypeAlias()
+    is WildcardTypeName -> unwrapTypeAliasReal()
+    is LambdaTypeName -> unwrapTypeAliasReal()
+    else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.")
+  }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt
new file mode 100644
index 0000000..993222e
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import kotlin.reflect.KClass
+
+annotation class ExampleAnnotation
+
+annotation class ComprehensiveAnnotation<T : CharSequence>(
+  val boolean: Boolean,
+  val booleanArray: BooleanArray,
+  val byte: Byte,
+  val byteArray: ByteArray,
+  val char: Char,
+  val charArray: CharArray,
+  val short: Short,
+  val shortArray: ShortArray,
+  val int: Int,
+  val intArray: IntArray,
+  val long: Long,
+  val longArray: LongArray,
+  val float: Float,
+  val floatArray: FloatArray,
+  val double: Double,
+  val doubleArray: DoubleArray,
+  val string: String,
+  val stringArray: Array<String>,
+  val someClass: KClass<*>,
+  val someClasses: Array<KClass<*>>,
+  val enumValue: AnnotationEnumValue,
+  val enumValueArray: Array<AnnotationEnumValue>,
+  val anotherAnnotation: AnotherAnnotation,
+  val anotherAnnotationArray: Array<AnotherAnnotation>,
+  // This is still included even when the argument is omitted until https://github.com/google/ksp/issues/674
+  val defaultingString: String = "defaultValue",
+)
+
+annotation class AnotherAnnotation(val input: String)
+
+enum class AnnotationEnumValue {
+  ONE, TWO, THREE
+}
diff --git a/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt
new file mode 100644
index 0000000..b392f37
--- /dev/null
+++ b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.common.truth.Truth.assertThat
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.Nullability
+import com.squareup.kotlinpoet.ksp.toTypeName
+import kotlin.test.assertFailsWith
+import org.junit.Test
+
+class KsTypesTest {
+  // Regression test for https://github.com/square/kotlinpoet/issues/1178
+  @Test
+  fun errorTypesShouldFail() {
+    val type = object : KSType {
+      override val isError: Boolean = true
+
+      // Boilerplate
+      override val annotations: Sequence<KSAnnotation>
+        get() = throw NotImplementedError()
+      override val arguments: List<KSTypeArgument>
+        get() = throw NotImplementedError()
+      override val declaration: KSDeclaration
+        get() = throw NotImplementedError()
+      override val isFunctionType: Boolean
+        get() = throw NotImplementedError()
+      override val isMarkedNullable: Boolean
+        get() = throw NotImplementedError()
+      override val isSuspendFunctionType: Boolean
+        get() = throw NotImplementedError()
+      override val nullability: Nullability
+        get() = throw NotImplementedError()
+
+      override fun isAssignableFrom(that: KSType): Boolean {
+        throw NotImplementedError()
+      }
+
+      override fun isCovarianceFlexible(): Boolean {
+        throw NotImplementedError()
+      }
+
+      override fun isMutabilityFlexible(): Boolean {
+        throw NotImplementedError()
+      }
+
+      override fun makeNotNullable(): KSType {
+        throw NotImplementedError()
+      }
+
+      override fun makeNullable(): KSType {
+        throw NotImplementedError()
+      }
+
+      override fun replace(arguments: List<KSTypeArgument>): KSType {
+        throw NotImplementedError()
+      }
+
+      override fun starProjection(): KSType {
+        throw NotImplementedError()
+      }
+    }
+
+    val exception = assertFailsWith<IllegalArgumentException> {
+      type.toTypeName()
+    }
+    assertThat(exception).hasMessageThat()
+      .contains("is not resolvable in the current round of processing")
+  }
+}
diff --git a/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt
new file mode 100644
index 0000000..06518ef
--- /dev/null
+++ b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.common.truth.Truth.assertThat
+import com.tschuchort.compiletesting.KotlinCompilation
+import com.tschuchort.compiletesting.SourceFile
+import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
+import com.tschuchort.compiletesting.kspArgs
+import com.tschuchort.compiletesting.kspIncremental
+import com.tschuchort.compiletesting.kspSourcesDir
+import com.tschuchort.compiletesting.symbolProcessorProviders
+import java.io.File
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class TestProcessorTest {
+
+  @Rule
+  @JvmField
+  val temporaryFolder: TemporaryFolder = TemporaryFolder()
+
+  @Test
+  fun smokeTest() {
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue
+           import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
+           import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           typealias TypeAliasName = String
+           typealias GenericTypeAlias = List<String>
+           typealias ParameterizedTypeAlias<T> = List<T>
+
+           @ComprehensiveAnnotation<String>(
+             true, // Omit the name intentionally here to test names are still picked up
+             booleanArray = [true],
+             byte = 0.toByte(),
+             byteArray = [0.toByte()],
+             char = 'a',
+             charArray = ['a', 'b', 'c'],
+             short = 0.toShort(),
+             shortArray = [0.toShort()],
+             int = 0,
+             intArray = [0],
+             long = 0L,
+             longArray = [0L],
+             float = 0f,
+             floatArray = [0f],
+             double = 0.0,
+             doubleArray = [0.0],
+             string = "Hello",
+             stringArray = ["Hello"],
+             someClass = String::class,
+             someClasses = [String::class, Int::class],
+             enumValue = AnnotationEnumValue.ONE,
+             enumValueArray = [AnnotationEnumValue.ONE, AnnotationEnumValue.TWO],
+             anotherAnnotation = AnotherAnnotation("Hello"),
+             anotherAnnotationArray = [AnotherAnnotation("Hello")]
+           )
+           @ExampleAnnotation
+           class SmokeTestClass<T, R : Any, E : Enum<E>> {
+             @field:AnotherAnnotation("siteTargeting")
+             private val propA: String = ""
+             internal val propB: String = ""
+             val propC: Int = 0
+             val propD: Int? = null
+             lateinit var propE: String
+             var propF: T? = null
+
+             fun functionA(): String {
+               error()
+             }
+
+             fun functionB(): R {
+               error()
+             }
+
+             fun <F> functionC(param1: String, param2: T, param3: F, param4: F?): R {
+               error()
+             }
+
+             suspend fun functionD(
+               param1: () -> String,
+               param2: (String) -> String,
+               param3: String.() -> String
+             ) {
+             }
+
+             // A whole bunch of wild types from Moshi's codegen smoke tests
+             fun wildTypes(
+               age: Int,
+               nationalities: List<String>,
+               weight: Float,
+               tattoos: Boolean = false,
+               race: String?,
+               hasChildren: Boolean = false,
+               favoriteFood: String? = null,
+               favoriteDrink: String? = "Water",
+               wildcardOut: MutableList<out String>,
+               nullableWildcardOut: MutableList<out String?>,
+               wildcardIn: Array<in String>,
+               any: List<*>,
+               anyTwo: List<Any>,
+               anyOut: MutableList<out Any>,
+               nullableAnyOut: MutableList<out Any?>,
+               favoriteThreeNumbers: IntArray,
+               favoriteArrayValues: Array<String>,
+               favoriteNullableArrayValues: Array<String?>,
+               nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>?,
+               // These are actually currently rendered incorrectly and always unwrapped
+               aliasedName: TypeAliasName,
+               genericAlias: GenericTypeAlias,
+               parameterizedTypeAlias: ParameterizedTypeAlias<String>,
+               nestedArray: Array<Map<String, Any>>?
+             ) {
+
+             }
+           }
+           """,
+      ),
+    )
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestSmokeTestClass.kt")
+      .readText()
+    assertThat(generatedFileText).isEqualTo(
+      """
+      package test
+
+      import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue
+      import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
+      import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
+      import kotlin.Any
+      import kotlin.Array
+      import kotlin.Boolean
+      import kotlin.Enum
+      import kotlin.Float
+      import kotlin.Function0
+      import kotlin.Function1
+      import kotlin.Int
+      import kotlin.IntArray
+      import kotlin.String
+      import kotlin.Unit
+      import kotlin.collections.List
+      import kotlin.collections.Map
+      import kotlin.collections.MutableList
+      import kotlin.collections.Set
+
+      @ComprehensiveAnnotation<String>(
+        boolean = true,
+        booleanArray = booleanArrayOf(true),
+        byte = 0.toByte(),
+        byteArray = byteArrayOf(0.toByte()),
+        char = 'a',
+        charArray = charArrayOf('a', 'b', 'c'),
+        short = 0.toShort(),
+        shortArray = shortArrayOf(0.toShort()),
+        int = 0,
+        intArray = intArrayOf(0),
+        long = 0,
+        longArray = longArrayOf(0),
+        float = 0.0f,
+        floatArray = floatArrayOf(0.0f),
+        double = 0.0,
+        doubleArray = doubleArrayOf(0.0),
+        string = "Hello",
+        stringArray = arrayOf("Hello"),
+        someClass = String::class,
+        someClasses = arrayOf(String::class, Int::class),
+        enumValue = AnnotationEnumValue.ONE,
+        enumValueArray = arrayOf(AnnotationEnumValue.ONE, AnnotationEnumValue.TWO),
+        anotherAnnotation = AnotherAnnotation(input = "Hello"),
+        anotherAnnotationArray = arrayOf(AnotherAnnotation(input = "Hello")),
+        defaultingString = "defaultValue",
+      )
+      public class SmokeTestClass<T, R : Any, E : Enum<E>> {
+        @field:AnotherAnnotation(input = "siteTargeting")
+        private val propA: String
+
+        internal val propB: String
+
+        public val propC: Int
+
+        public val propD: Int?
+
+        public lateinit var propE: String
+
+        public var propF: T?
+
+        public fun functionA(): String {
+        }
+
+        public fun functionB(): R {
+        }
+
+        public fun <F> functionC(
+          param1: String,
+          param2: T,
+          param3: F,
+          param4: F?,
+        ): R {
+        }
+
+        public suspend fun functionD(
+          param1: Function0<String>,
+          param2: Function1<String, String>,
+          param3: Function1<String, String>,
+        ): Unit {
+        }
+
+        public fun wildTypes(
+          age: Int,
+          nationalities: List<String>,
+          weight: Float,
+          tattoos: Boolean,
+          race: String?,
+          hasChildren: Boolean,
+          favoriteFood: String?,
+          favoriteDrink: String?,
+          wildcardOut: MutableList<out String>,
+          nullableWildcardOut: MutableList<out String?>,
+          wildcardIn: Array<in String>,
+          any: List<*>,
+          anyTwo: List<Any>,
+          anyOut: MutableList<out Any>,
+          nullableAnyOut: MutableList<*>,
+          favoriteThreeNumbers: IntArray,
+          favoriteArrayValues: Array<String>,
+          favoriteNullableArrayValues: Array<String?>,
+          nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>?,
+          aliasedName: TypeAliasName,
+          genericAlias: GenericTypeAlias,
+          parameterizedTypeAlias: ParameterizedTypeAlias<String>,
+          nestedArray: Array<Map<String, Any>>?,
+        ): Unit {
+        }
+      }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun unwrapTypeAliases() {
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           typealias TypeAliasName = String
+           typealias GenericTypeAlias = List<String>
+           typealias GenericMapTypeAlias<V, K> = Map<K, V>
+           typealias T1Unused<T1, T2> = Map<T2, String>
+           typealias A1<T1, T2> = A2<T2, T1>
+           typealias A2<T2, T3> = Map<T3, T2>
+
+           @ExampleAnnotation
+           class Example {
+             fun aliases(
+               aliasedName: TypeAliasName,
+               genericAlias: GenericTypeAlias,
+               genericMapAlias: GenericMapTypeAlias<String, Int>,
+               t1Unused: T1Unused<String, Int>,
+               a1: A1<String, Int>,
+             ) {
+             }
+           }
+           """,
+      ),
+    )
+    compilation.kspArgs["unwrapTypeAliases"] = "true"
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestExample.kt")
+      .readText()
+    assertThat(generatedFileText).isEqualTo(
+      """
+      package test
+
+      import kotlin.Int
+      import kotlin.String
+      import kotlin.Unit
+      import kotlin.collections.List
+      import kotlin.collections.Map
+
+      public class Example {
+        public fun aliases(
+          aliasedName: String,
+          genericAlias: List<String>,
+          genericMapAlias: Map<Int, String>,
+          t1Unused: Map<Int, String>,
+          a1: Map<String, Int>,
+        ): Unit {
+        }
+      }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun complexSelfReferencingTypeArgs() {
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           @ExampleAnnotation
+           open class Node<T : Node<T, R>, R : Node<R, T>> {
+             var t: T? = null
+             var r: R? = null
+           }
+           """,
+      ),
+    )
+
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestNode.kt")
+      .readText()
+    assertThat(generatedFileText).isEqualTo(
+      """
+      package test
+
+      public open class Node<T : Node<T, R>, R : Node<R, T>> {
+        public var t: T?
+
+        public var r: R?
+      }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun wildcardParameterForRecursiveTypeBound() {
+    // Enum is an example of a recursive type bound - Enum<E: Enum<E>>
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           @ExampleAnnotation
+           class EnumWrapper {
+            val enumValue: Enum<*>
+           }
+           """,
+      ),
+    )
+
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestEnumWrapper.kt")
+      .readText()
+    assertThat(generatedFileText).isEqualTo(
+      """
+      package test
+
+      import kotlin.Enum
+
+      public class EnumWrapper {
+        public val enumValue: Enum<*>
+      }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun transitiveAliases() {
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           typealias Alias23 = (Any) -> Any
+           typealias Alias77<Q> = List<Q>
+           typealias Alias73<Q> = Map<String, Q>
+           typealias Alias55<Q> = Alias73<Q>
+           typealias Alias99<Q> = Alias55<Q>
+           typealias Alias43<Q> = Alias77<Q>
+           typealias Alias47<Q> = Alias43<Q>
+           typealias Alias41<Z, Q> = (Alias43<Z>) -> Alias47<Q>
+
+           @ExampleAnnotation
+           interface TransitiveAliases {
+              fun <T : Alias41<Alias23, out Alias77<Alias73<Int>>>> bar(vararg arg1: T)
+           }
+           """,
+      ),
+    )
+
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestTransitiveAliases.kt")
+      .readText()
+    assertThat(generatedFileText).isEqualTo(
+      """
+    package test
+
+    import kotlin.Int
+    import kotlin.Unit
+
+    public class TransitiveAliases {
+      public fun <T : Alias41<Alias23, out Alias77<Alias73<Int>>>> bar(arg1: T): Unit {
+      }
+    }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test
+  fun aliasAsTypeArgument() {
+    val compilation = prepareCompilation(
+      kotlin(
+        "Example.kt",
+        """
+           package test
+
+           import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+           typealias Alias997 = Map<String, Int>
+
+           @ExampleAnnotation
+           interface AliasAsTypeArgument {
+              fun bar(arg1: List<Alias997>)
+           }
+           """,
+      ),
+    )
+
+    val result = compilation.compile()
+    assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+    val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestAliasAsTypeArgument.kt")
+      .readText()
+
+    assertThat(generatedFileText).isEqualTo(
+      """
+    package test
+
+    import kotlin.Unit
+    import kotlin.collections.List
+
+    public class AliasAsTypeArgument {
+      public fun bar(arg1: List<Alias997>): Unit {
+      }
+    }
+
+      """.trimIndent(),
+    )
+  }
+
+  private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
+    return KotlinCompilation()
+      .apply {
+        workingDir = temporaryFolder.root
+        inheritClassPath = true
+        symbolProcessorProviders = listOf(TestProcessorProvider())
+        sources = sourceFiles.asList()
+        verbose = false
+        kspIncremental = true // The default now
+      }
+  }
+}
diff --git a/kotlinpoet/api/kotlinpoet.api b/kotlinpoet/api/kotlinpoet.api
new file mode 100644
index 0000000..59f04dc
--- /dev/null
+++ b/kotlinpoet/api/kotlinpoet.api
@@ -0,0 +1,1164 @@
+public final class com/squareup/kotlinpoet/AnnotationSpec : com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/AnnotationSpec$Companion;
+	public static final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public static final fun builder (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public static final fun builder (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public static final fun builder (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public static final fun get (Ljava/lang/annotation/Annotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public static final fun get (Ljava/lang/annotation/Annotation;Z)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public static final fun get (Ljavax/lang/model/element/AnnotationMirror;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun getMembers ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeName ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getUseSiteTarget ()Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public fun hashCode ()I
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+	public static final field Companion Lcom/squareup/kotlinpoet/AnnotationSpec$Builder$Companion;
+	public final fun addMember (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun addMember (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public final fun getMembers ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public final fun useSiteTarget (Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Builder$Companion {
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Companion {
+	public final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun builder (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun builder (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun builder (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+	public final fun get (Ljava/lang/annotation/Annotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public final fun get (Ljava/lang/annotation/Annotation;Z)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public final fun get (Ljavax/lang/model/element/AnnotationMirror;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/AnnotationSpec$Companion;Ljava/lang/annotation/Annotation;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget : java/lang/Enum {
+	public static final field DELEGATE Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field FIELD Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field FILE Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field GET Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field PARAM Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field PROPERTY Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field RECEIVER Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field SET Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static final field SETPARAM Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+	public static fun values ()[Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+}
+
+public final class com/squareup/kotlinpoet/ClassName : com/squareup/kotlinpoet/TypeName, java/lang/Comparable {
+	public static final field Companion Lcom/squareup/kotlinpoet/ClassName$Companion;
+	public fun <init> (Ljava/lang/String;)V
+	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V
+	public fun <init> (Ljava/lang/String;Ljava/util/List;)V
+	public fun <init> (Ljava/lang/String;[Ljava/lang/String;)V
+	public static final fun bestGuess (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+	public fun compareTo (Lcom/squareup/kotlinpoet/ClassName;)I
+	public synthetic fun compareTo (Ljava/lang/Object;)I
+	public final fun constructorReference ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/ClassName;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public final fun enclosingClassName ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun getCanonicalName ()Ljava/lang/String;
+	public final fun getPackageName ()Ljava/lang/String;
+	public final fun getSimpleName ()Ljava/lang/String;
+	public final fun getSimpleNames ()Ljava/util/List;
+	public final fun nestedClass (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+	public final fun peerClass (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+	public final fun reflectionName ()Ljava/lang/String;
+	public final fun topLevelClassName ()Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ClassName$Companion {
+	public final fun bestGuess (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ClassNames {
+	public static final fun get (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ClassName;
+	public static final fun get (Ljavax/lang/model/element/TypeElement;)Lcom/squareup/kotlinpoet/ClassName;
+	public static final fun get (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock {
+	public static final field Companion Lcom/squareup/kotlinpoet/CodeBlock$Companion;
+	public synthetic fun <init> (Ljava/util/List;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public static final fun builder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public fun hashCode ()I
+	public final fun isEmpty ()Z
+	public final fun isNotEmpty ()Z
+	public static final fun of (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock$Builder {
+	public fun <init> ()V
+	public final fun add (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun add (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun addNamed (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun clear ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun endControlFlow ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun indent ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun isEmpty ()Z
+	public final fun isNotEmpty ()Z
+	public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun unindent ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock$Companion {
+	public final fun builder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun of (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlocks {
+	public static final fun buildCodeBlock (Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static final fun joinToCode (Ljava/util/Collection;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static synthetic fun joinToCode$default (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+	public static final fun withIndent (Lcom/squareup/kotlinpoet/CodeBlock$Builder;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ContextReceivable {
+	public abstract fun getContextReceiverTypes ()Ljava/util/List;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ContextReceivable$Builder {
+	public abstract fun getContextReceiverTypes ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/ContextReceivable$Builder$DefaultImpls {
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/DelicateKotlinPoetApi : java/lang/annotation/Annotation {
+	public abstract fun message ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/Dynamic : com/squareup/kotlinpoet/TypeName {
+	public static final field INSTANCE Lcom/squareup/kotlinpoet/Dynamic;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Ljava/lang/Void;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/ExperimentalKotlinPoetApi : java/lang/annotation/Annotation {
+}
+
+public final class com/squareup/kotlinpoet/FileSpec : com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/FileSpec$Companion;
+	public static final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public static final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec;
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getBody ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getComment ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getDefaultImports ()Ljava/util/Set;
+	public final fun getMembers ()Ljava/util/List;
+	public final fun getName ()Ljava/lang/String;
+	public final fun getPackageName ()Ljava/lang/String;
+	public fun getTags ()Ljava/util/Map;
+	public fun hashCode ()I
+	public final fun isScript ()Z
+	public static final fun scriptBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/FileSpec;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun toJavaFileObject ()Ljavax/tools/JavaFileObject;
+	public fun toString ()Ljava/lang/String;
+	public final fun writeTo (Ljava/io/File;)V
+	public final fun writeTo (Ljava/lang/Appendable;)V
+	public final fun writeTo (Ljava/nio/file/Path;)V
+	public final fun writeTo (Ljavax/annotation/processing/Filer;)V
+}
+
+public final class com/squareup/kotlinpoet/FileSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAliasedImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAliasedImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAliasedImport (Lcom/squareup/kotlinpoet/MemberName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAliasedImport (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAliasedImport (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addBodyComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addCode (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addCode (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addDefaultPackageImport (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addFileComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addFunction (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Lcom/squareup/kotlinpoet/ClassName;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Lcom/squareup/kotlinpoet/Import;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Ljava/lang/Class;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Ljava/lang/Enum;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Ljava/lang/String;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addImport (Lkotlin/reflect/KClass;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addKotlinDefaultImports (ZZ)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static synthetic fun addKotlinDefaultImports$default (Lcom/squareup/kotlinpoet/FileSpec$Builder;ZZILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addNamedCode (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addProperty (Lcom/squareup/kotlinpoet/PropertySpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addType (Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun addTypeAlias (Lcom/squareup/kotlinpoet/TypeAliasSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/FileSpec;
+	public final fun clearBody ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun clearComment ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun clearImports ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun endControlFlow ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getDefaultImports ()Ljava/util/Set;
+	public final fun getImports ()Ljava/util/List;
+	public final fun getMembers ()Ljava/util/List;
+	public final fun getName ()Ljava/lang/String;
+	public final fun getPackageName ()Ljava/lang/String;
+	public fun getTags ()Ljava/util/Map;
+	public final fun indent (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun isScript ()Z
+	public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FileSpec$Companion {
+	public final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec;
+	public final fun scriptBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static synthetic fun scriptBuilder$default (Lcom/squareup/kotlinpoet/FileSpec$Companion;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/FunSpec$Companion;
+	public static final fun builder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun constructorBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getBody ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public fun getContextReceiverTypes ()Ljava/util/List;
+	public final fun getDelegateConstructor ()Ljava/lang/String;
+	public final fun getDelegateConstructorArguments ()Ljava/util/List;
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getModifiers ()Ljava/util/Set;
+	public final fun getName ()Ljava/lang/String;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public final fun getParameters ()Ljava/util/List;
+	public final fun getReceiverKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getReceiverType ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getReturnKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getReturnType ()Lcom/squareup/kotlinpoet/TypeName;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public static final fun getterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun hashCode ()I
+	public final fun isAccessor ()Z
+	public final fun isConstructor ()Z
+	public static final fun overriding (Ljavax/lang/model/element/ExecutableElement;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun overriding (Ljavax/lang/model/element/ExecutableElement;Ljavax/lang/model/type/DeclaredType;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun setterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/FunSpec;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addCode (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addCode (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addNamedCode (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+	public final fun addParameter (Lcom/squareup/kotlinpoet/ParameterSpec;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameter (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addParameters (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/FunSpec;
+	public final fun callSuperConstructor (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callSuperConstructor (Ljava/util/List;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callSuperConstructor ([Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callSuperConstructor ([Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun callSuperConstructor$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callThisConstructor (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callThisConstructor (Ljava/util/List;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callThisConstructor ([Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun callThisConstructor ([Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun callThisConstructor$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun clearBody ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public final fun endControlFlow ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun getAnnotations ()Ljava/util/List;
+	public fun getContextReceiverTypes ()Ljava/util/List;
+	public final fun getModifiers ()Ljava/util/List;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public final fun getParameters ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public final fun jvmModifiers (Ljava/lang/Iterable;)V
+	public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Ljava/lang/reflect/Type;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun receiver (Lkotlin/reflect/KClass;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Ljava/lang/reflect/Type;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun returns (Lkotlin/reflect/KClass;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec$Companion {
+	public final fun builder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun constructorBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun getterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun overriding (Ljavax/lang/model/element/ExecutableElement;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun overriding (Ljavax/lang/model/element/ExecutableElement;Ljavax/lang/model/type/DeclaredType;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public final fun setterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Import : java/lang/Comparable {
+	public fun compareTo (Lcom/squareup/kotlinpoet/Import;)I
+	public synthetic fun compareTo (Ljava/lang/Object;)I
+	public final fun component1 ()Ljava/lang/String;
+	public final fun component2 ()Ljava/lang/String;
+	public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/Import;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/Import;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/Import;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAlias ()Ljava/lang/String;
+	public final fun getQualifiedName ()Ljava/lang/String;
+	public fun hashCode ()I
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/KModifier : java/lang/Enum {
+	public static final field ABSTRACT Lcom/squareup/kotlinpoet/KModifier;
+	public static final field ACTUAL Lcom/squareup/kotlinpoet/KModifier;
+	public static final field ANNOTATION Lcom/squareup/kotlinpoet/KModifier;
+	public static final field COMPANION Lcom/squareup/kotlinpoet/KModifier;
+	public static final field CONST Lcom/squareup/kotlinpoet/KModifier;
+	public static final field CROSSINLINE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field DATA Lcom/squareup/kotlinpoet/KModifier;
+	public static final field ENUM Lcom/squareup/kotlinpoet/KModifier;
+	public static final field EXPECT Lcom/squareup/kotlinpoet/KModifier;
+	public static final field EXTERNAL Lcom/squareup/kotlinpoet/KModifier;
+	public static final field FINAL Lcom/squareup/kotlinpoet/KModifier;
+	public static final field FUN Lcom/squareup/kotlinpoet/KModifier;
+	public static final field IN Lcom/squareup/kotlinpoet/KModifier;
+	public static final field INFIX Lcom/squareup/kotlinpoet/KModifier;
+	public static final field INLINE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field INNER Lcom/squareup/kotlinpoet/KModifier;
+	public static final field INTERNAL Lcom/squareup/kotlinpoet/KModifier;
+	public static final field LATEINIT Lcom/squareup/kotlinpoet/KModifier;
+	public static final field NOINLINE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field OPEN Lcom/squareup/kotlinpoet/KModifier;
+	public static final field OPERATOR Lcom/squareup/kotlinpoet/KModifier;
+	public static final field OUT Lcom/squareup/kotlinpoet/KModifier;
+	public static final field OVERRIDE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field PRIVATE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field PROTECTED Lcom/squareup/kotlinpoet/KModifier;
+	public static final field PUBLIC Lcom/squareup/kotlinpoet/KModifier;
+	public static final field REIFIED Lcom/squareup/kotlinpoet/KModifier;
+	public static final field SEALED Lcom/squareup/kotlinpoet/KModifier;
+	public static final field SUSPEND Lcom/squareup/kotlinpoet/KModifier;
+	public static final field TAILREC Lcom/squareup/kotlinpoet/KModifier;
+	public static final field VALUE Lcom/squareup/kotlinpoet/KModifier;
+	public static final field VARARG Lcom/squareup/kotlinpoet/KModifier;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/KModifier;
+	public static fun values ()[Lcom/squareup/kotlinpoet/KModifier;
+}
+
+public final class com/squareup/kotlinpoet/KOperator : java/lang/Enum {
+	public static final field CONTAINS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field DEC Lcom/squareup/kotlinpoet/KOperator;
+	public static final field DIV Lcom/squareup/kotlinpoet/KOperator;
+	public static final field DIV_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+	public static final field EQUALS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field GE Lcom/squareup/kotlinpoet/KOperator;
+	public static final field GT Lcom/squareup/kotlinpoet/KOperator;
+	public static final field INC Lcom/squareup/kotlinpoet/KOperator;
+	public static final field ITERATOR Lcom/squareup/kotlinpoet/KOperator;
+	public static final field LE Lcom/squareup/kotlinpoet/KOperator;
+	public static final field LT Lcom/squareup/kotlinpoet/KOperator;
+	public static final field MINUS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field MINUS_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+	public static final field NOT Lcom/squareup/kotlinpoet/KOperator;
+	public static final field NOT_CONTAINS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field NOT_EQUALS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field PLUS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field PLUS_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+	public static final field RANGE_TO Lcom/squareup/kotlinpoet/KOperator;
+	public static final field REM Lcom/squareup/kotlinpoet/KOperator;
+	public static final field REM_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+	public static final field TIMES Lcom/squareup/kotlinpoet/KOperator;
+	public static final field TIMES_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+	public static final field UNARY_MINUS Lcom/squareup/kotlinpoet/KOperator;
+	public static final field UNARY_PLUS Lcom/squareup/kotlinpoet/KOperator;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/KOperator;
+	public static fun values ()[Lcom/squareup/kotlinpoet/KOperator;
+}
+
+public final class com/squareup/kotlinpoet/LambdaTypeName : com/squareup/kotlinpoet/TypeName {
+	public static final field Companion Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public final fun copy (ZLjava/util/List;ZLjava/util/Map;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/LambdaTypeName;ZLjava/util/List;ZLjava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public final fun getParameters ()Ljava/util/List;
+	public final fun getReceiver ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getReturnType ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun isSuspending ()Z
+}
+
+public final class com/squareup/kotlinpoet/LambdaTypeName$Companion {
+	public final fun get (Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+}
+
+public final class com/squareup/kotlinpoet/MemberName {
+	public static final field Companion Lcom/squareup/kotlinpoet/MemberName$Companion;
+	public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/KOperator;)V
+	public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)V
+	public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Z)V
+	public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;)V
+	public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
+	public fun <init> (Ljava/lang/String;Ljava/lang/String;Z)V
+	public final fun component1 ()Ljava/lang/String;
+	public final fun component2 ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun component3 ()Ljava/lang/String;
+	public final fun component4 ()Lcom/squareup/kotlinpoet/KOperator;
+	public final fun component5 ()Z
+	public final fun copy (Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;Z)Lcom/squareup/kotlinpoet/MemberName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/MemberName;Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/MemberName;
+	public fun equals (Ljava/lang/Object;)Z
+	public static final fun get (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+	public static final fun get (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+	public final fun getCanonicalName ()Ljava/lang/String;
+	public final fun getEnclosingClassName ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun getOperator ()Lcom/squareup/kotlinpoet/KOperator;
+	public final fun getPackageName ()Ljava/lang/String;
+	public final fun getSimpleName ()Ljava/lang/String;
+	public fun hashCode ()I
+	public final fun isExtension ()Z
+	public static final synthetic fun member (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+	public final fun reference ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/MemberName$Companion {
+	public final fun get (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+	public final fun get (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+	public final synthetic fun member (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+}
+
+public final class com/squareup/kotlinpoet/NameAllocator {
+	public fun <init> ()V
+	public final fun copy ()Lcom/squareup/kotlinpoet/NameAllocator;
+	public final fun get (Ljava/lang/Object;)Ljava/lang/String;
+	public final fun newName (Ljava/lang/String;)Ljava/lang/String;
+	public final fun newName (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
+	public static synthetic fun newName$default (Lcom/squareup/kotlinpoet/NameAllocator;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/String;
+}
+
+public abstract interface class com/squareup/kotlinpoet/OriginatingElementsHolder {
+	public abstract fun getOriginatingElements ()Ljava/util/List;
+}
+
+public abstract interface class com/squareup/kotlinpoet/OriginatingElementsHolder$Builder {
+	public abstract fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+	public abstract fun getOriginatingElements ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/OriginatingElementsHolder$Builder$DefaultImpls {
+	public static fun addOriginatingElement (Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec : com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/ParameterSpec$Companion;
+	public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)V
+	public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)V
+	public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public static final fun get (Ljavax/lang/model/element/VariableElement;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getDefaultValue ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getModifiers ()Ljava/util/Set;
+	public final fun getName ()Ljava/lang/String;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+	public fun hashCode ()I
+	public static final fun parametersOf (Ljavax/lang/model/element/ExecutableElement;)Ljava/util/List;
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/ParameterSpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public fun toString ()Ljava/lang/String;
+	public static final fun unnamed (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public static final fun unnamed (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public static final fun unnamed (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/ParameterSpec;
+	public final fun defaultValue (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun defaultValue (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+	public final fun getModifiers ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public final fun jvmModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec$Companion {
+	public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+	public final fun get (Ljavax/lang/model/element/VariableElement;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public final fun parametersOf (Ljavax/lang/model/element/ExecutableElement;)Ljava/util/List;
+	public final fun unnamed (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public final fun unnamed (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterSpec;
+	public final fun unnamed (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeName : com/squareup/kotlinpoet/TypeName {
+	public static final field Companion Lcom/squareup/kotlinpoet/ParameterizedTypeName$Companion;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public final fun copy (ZLjava/util/List;Ljava/util/Map;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/ParameterizedTypeName;ZLjava/util/List;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/ClassName;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lcom/squareup/kotlinpoet/ClassName;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Ljava/lang/Class;Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Ljava/lang/Class;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public static final fun get (Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun getRawType ()Lcom/squareup/kotlinpoet/ClassName;
+	public final fun getTypeArguments ()Ljava/util/List;
+	public final fun nestedClass (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun plusParameter (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun plusParameter (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun plusParameter (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeName$Companion {
+	public final fun get (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Lcom/squareup/kotlinpoet/ClassName;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Lcom/squareup/kotlinpoet/ClassName;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Ljava/lang/Class;Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Ljava/lang/Class;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+	public final fun get (Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeNames {
+	public static final fun asTypeName (Lkotlin/reflect/KType;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun get (Ljava/lang/reflect/ParameterizedType;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/PropertySpec$Companion;
+	public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAnnotations ()Ljava/util/List;
+	public fun getContextReceiverTypes ()Ljava/util/List;
+	public final fun getDelegated ()Z
+	public final fun getGetter ()Lcom/squareup/kotlinpoet/FunSpec;
+	public final fun getInitializer ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getModifiers ()Ljava/util/Set;
+	public final fun getMutable ()Z
+	public final fun getName ()Ljava/lang/String;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public final fun getReceiverType ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getSetter ()Lcom/squareup/kotlinpoet/FunSpec;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/PropertySpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+	public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/PropertySpec;
+	public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public final fun delegate (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun delegate (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun getAnnotations ()Ljava/util/List;
+	public fun getContextReceiverTypes ()Ljava/util/List;
+	public final fun getModifiers ()Ljava/util/List;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public final fun getter (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun initializer (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun initializer (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun mutable (Z)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static synthetic fun mutable$default (Lcom/squareup/kotlinpoet/PropertySpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun receiver (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun receiver (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun setter (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec$Companion {
+	public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+}
+
+public abstract interface class com/squareup/kotlinpoet/Taggable {
+	public abstract fun getTags ()Ljava/util/Map;
+	public abstract fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public abstract fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+}
+
+public abstract interface class com/squareup/kotlinpoet/Taggable$Builder {
+	public abstract fun getTags ()Ljava/util/Map;
+	public abstract fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public abstract fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Taggable$Builder$DefaultImpls {
+	public static fun tag (Lcom/squareup/kotlinpoet/Taggable$Builder;Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public static fun tag (Lcom/squareup/kotlinpoet/Taggable$Builder;Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Taggable$DefaultImpls {
+	public static fun getTags (Lcom/squareup/kotlinpoet/Taggable;)Ljava/util/Map;
+	public static fun tag (Lcom/squareup/kotlinpoet/Taggable;Ljava/lang/Class;)Ljava/lang/Object;
+	public static fun tag (Lcom/squareup/kotlinpoet/Taggable;Lkotlin/reflect/KClass;)Ljava/lang/Object;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec : com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/TypeAliasSpec$Companion;
+	public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getModifiers ()Ljava/util/Set;
+	public final fun getName ()Ljava/lang/String;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public fun hashCode ()I
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/TypeAliasSpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/TypeAliasSpec;
+	public final fun getAnnotations ()Ljava/util/List;
+	public final fun getModifiers ()Ljava/util/Set;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeVariables ()Ljava/util/Set;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec$Companion {
+	public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+	public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+}
+
+public abstract class com/squareup/kotlinpoet/TypeName : com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/TypeName$Companion;
+	public synthetic fun <init> (ZLjava/util/List;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun copy (ZLjava/util/List;)Lcom/squareup/kotlinpoet/TypeName;
+	public abstract fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeName;ZLjava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeName;ZLjava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public fun equals (Ljava/lang/Object;)Z
+	public final fun getAnnotations ()Ljava/util/List;
+	public fun getTags ()Ljava/util/Map;
+	public fun hashCode ()I
+	public final fun isAnnotated ()Z
+	public final fun isNullable ()Z
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/TypeName$Companion {
+}
+
+public final class com/squareup/kotlinpoet/TypeNames {
+	public static final field ANNOTATION Lcom/squareup/kotlinpoet/ClassName;
+	public static final field ANY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field BOOLEAN Lcom/squareup/kotlinpoet/ClassName;
+	public static final field BOOLEAN_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field BYTE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field BYTE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field CHAR Lcom/squareup/kotlinpoet/ClassName;
+	public static final field CHAR_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field CHAR_SEQUENCE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field COLLECTION Lcom/squareup/kotlinpoet/ClassName;
+	public static final field COMPARABLE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field DOUBLE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field DOUBLE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field DYNAMIC Lcom/squareup/kotlinpoet/Dynamic;
+	public static final field ENUM Lcom/squareup/kotlinpoet/ClassName;
+	public static final field FLOAT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field FLOAT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field INT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field INT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field ITERABLE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field LIST Lcom/squareup/kotlinpoet/ClassName;
+	public static final field LONG Lcom/squareup/kotlinpoet/ClassName;
+	public static final field LONG_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MAP Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MAP_ENTRY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_COLLECTION Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_ITERABLE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_LIST Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_MAP Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_MAP_ENTRY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field MUTABLE_SET Lcom/squareup/kotlinpoet/ClassName;
+	public static final field NOTHING Lcom/squareup/kotlinpoet/ClassName;
+	public static final field NUMBER Lcom/squareup/kotlinpoet/ClassName;
+	public static final field SET Lcom/squareup/kotlinpoet/ClassName;
+	public static final field SHORT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field SHORT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field STAR Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public static final field STRING Lcom/squareup/kotlinpoet/ClassName;
+	public static final field THROWABLE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field UNIT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_BYTE Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_BYTE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_INT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_INT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_LONG Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_LONG_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_SHORT Lcom/squareup/kotlinpoet/ClassName;
+	public static final field U_SHORT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+	public static final fun get (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun get (Ljavax/lang/model/type/TypeMirror;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun get (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+	public static final field Companion Lcom/squareup/kotlinpoet/TypeSpec$Companion;
+	public static final fun annotationBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun annotationBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun anonymousClassBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun classBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun classBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun companionObjectBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun companionObjectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun enumBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun enumBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public fun equals (Ljava/lang/Object;)Z
+	public static final fun expectClassBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun expectClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun funInterfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun funInterfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun getAnnotationSpecs ()Ljava/util/List;
+	public fun getContextReceiverTypes ()Ljava/util/List;
+	public final fun getEnumConstants ()Ljava/util/Map;
+	public final fun getFunSpecs ()Ljava/util/List;
+	public final fun getInitializerBlock ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getInitializerIndex ()I
+	public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+	public final fun getKind ()Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+	public final fun getModifiers ()Ljava/util/Set;
+	public final fun getName ()Ljava/lang/String;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public final fun getPrimaryConstructor ()Lcom/squareup/kotlinpoet/FunSpec;
+	public final fun getPropertySpecs ()Ljava/util/List;
+	public final fun getSuperclass ()Lcom/squareup/kotlinpoet/TypeName;
+	public final fun getSuperclassConstructorParameters ()Ljava/util/List;
+	public final fun getSuperinterfaces ()Ljava/util/Map;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeSpecs ()Ljava/util/List;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public fun hashCode ()I
+	public static final fun interfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun interfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun isAnnotation ()Z
+	public final fun isAnonymousClass ()Z
+	public final fun isCompanion ()Z
+	public final fun isEnum ()Z
+	public final fun isFunctionalInterface ()Z
+	public static final fun objectBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun objectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+	public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+	public final fun toBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun toBuilder (Lcom/squareup/kotlinpoet/TypeSpec$Kind;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun toBuilder (Lcom/squareup/kotlinpoet/TypeSpec$Kind;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/TypeSpec;Lcom/squareup/kotlinpoet/TypeSpec$Kind;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public fun toString ()Ljava/lang/String;
+	public static final fun valueClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addEnumConstant (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addEnumConstant (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun addEnumConstant$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addFunction (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addFunctions (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addInitializerBlock (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+	public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperties (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Lcom/squareup/kotlinpoet/PropertySpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addProperty (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperclassConstructorParameter (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperclassConstructorParameter (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterface (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterface (Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterface (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterface (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterface (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addSuperinterfaces (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addType (Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun addTypes (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun build ()Lcom/squareup/kotlinpoet/TypeSpec;
+	public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+	public final fun getAnnotationSpecs ()Ljava/util/List;
+	public final fun getEnumConstants ()Ljava/util/Map;
+	public final fun getFunSpecs ()Ljava/util/List;
+	public final fun getInitializerIndex ()I
+	public final fun getModifiers ()Ljava/util/Set;
+	public fun getOriginatingElements ()Ljava/util/List;
+	public final fun getPropertySpecs ()Ljava/util/List;
+	public final fun getSuperclassConstructorParameters ()Ljava/util/List;
+	public final fun getSuperinterfaces ()Ljava/util/Map;
+	public fun getTags ()Ljava/util/Map;
+	public final fun getTypeSpecs ()Ljava/util/List;
+	public final fun getTypeVariables ()Ljava/util/List;
+	public final fun primaryConstructor (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun setInitializerIndex (I)V
+	public final fun superclass (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun superclass (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun superclass (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+	public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Companion {
+	public final fun annotationBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun annotationBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun anonymousClassBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun classBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun classBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun companionObjectBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun companionObjectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun companionObjectBuilder$default (Lcom/squareup/kotlinpoet/TypeSpec$Companion;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun enumBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun enumBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun expectClassBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun expectClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun funInterfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun funInterfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun interfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun interfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun objectBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun objectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public final fun valueClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Kind : java/lang/Enum {
+	public static final field CLASS Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+	public static final field INTERFACE Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+	public static final field OBJECT Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+	public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+	public static fun values ()[Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableName : com/squareup/kotlinpoet/TypeName {
+	public static final field Companion Lcom/squareup/kotlinpoet/TypeVariableName$Companion;
+	public final fun copy (ZLjava/util/List;Ljava/util/List;ZLjava/util/Map;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeVariableName;ZLjava/util/List;Ljava/util/List;ZLjava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun getBounds ()Ljava/util/List;
+	public final fun getName ()Ljava/lang/String;
+	public final fun getVariance ()Lcom/squareup/kotlinpoet/KModifier;
+	public static final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun isReified ()Z
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableName$Companion {
+	public final fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun getWithClasses$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static synthetic fun getWithTypes$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableNames {
+	public static final fun asTypeVariableName (Lkotlin/reflect/KTypeParameter;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljavax/lang/model/element/TypeParameterElement;)Lcom/squareup/kotlinpoet/TypeVariableName;
+	public static final fun get (Ljavax/lang/model/type/TypeVariable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeName : com/squareup/kotlinpoet/TypeName {
+	public static final field Companion Lcom/squareup/kotlinpoet/WildcardTypeName$Companion;
+	public static final fun consumerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public static final fun consumerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public static final fun consumerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+	public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun getInTypes ()Ljava/util/List;
+	public final fun getOutTypes ()Ljava/util/List;
+	public static final fun producerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public static final fun producerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public static final fun producerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeName$Companion {
+	public final fun consumerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun consumerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun consumerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun producerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun producerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+	public final fun producerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeNames {
+	public static final fun get (Ljava/lang/reflect/WildcardType;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun get (Ljavax/lang/model/type/WildcardType;)Lcom/squareup/kotlinpoet/TypeName;
+}
+
+public final class com/squareup/kotlinpoet/jvm/JvmAnnotations {
+	public static final fun jvmDefault (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun jvmDefault (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun jvmField (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun jvmInline (Lcom/squareup/kotlinpoet/TypeSpec$Builder;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun jvmMultifileClass (Lcom/squareup/kotlinpoet/FileSpec$Builder;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static final fun jvmName (Lcom/squareup/kotlinpoet/FileSpec$Builder;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+	public static final fun jvmName (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun jvmOverloads (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun jvmRecord (Lcom/squareup/kotlinpoet/TypeSpec$Builder;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun jvmStatic (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun jvmStatic (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/FunSpec$Builder;Z)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/PropertySpec$Builder;Z)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/TypeName;Z)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Z)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/PropertySpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/TypeName;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+	public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+	public static final fun jvmWildcard (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeName;
+	public static final fun strictfp (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun synchronized (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+	public static final fun transient (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+	public static final fun volatile (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/tags/TypeAliasTag {
+	public fun <init> (Lcom/squareup/kotlinpoet/TypeName;)V
+	public final fun getAbbreviatedType ()Lcom/squareup/kotlinpoet/TypeName;
+}
+
diff --git a/kotlinpoet/build.gradle.kts b/kotlinpoet/build.gradle.kts
new file mode 100644
index 0000000..830f870
--- /dev/null
+++ b/kotlinpoet/build.gradle.kts
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+tasks.jar {
+  manifest {
+    attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet")
+  }
+}
+
+tasks.compileTestKotlin {
+  kotlinOptions {
+    freeCompilerArgs = listOf("-opt-in=com.squareup.kotlinpoet.DelicateKotlinPoetApi")
+  }
+}
+
+spotless {
+  kotlin {
+    targetExclude(
+      // Non-Square licensed files
+      "src/main/java/com/squareup/kotlinpoet/ClassName.kt",
+      "src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt",
+      "src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt",
+      "src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt",
+      "src/test/java/com/squareup/kotlinpoet/TypesTest.kt",
+    )
+  }
+}
+
+dependencies {
+  implementation(libs.kotlin.reflect)
+  testImplementation(libs.kotlin.junit)
+  testImplementation(libs.truth)
+  testImplementation(libs.compileTesting)
+  testImplementation(libs.jimfs)
+  testImplementation(libs.ecj)
+  testImplementation(libs.kotlinCompileTesting)
+  testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+  testImplementation(libs.kotlin.compilerEmbeddable)
+}
diff --git a/kotlinpoet/gradle.properties b/kotlinpoet/gradle.properties
new file mode 100644
index 0000000..7258dd1
--- /dev/null
+++ b/kotlinpoet/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet
+POM_NAME=KotlinPoet
+POM_DESCRIPTION=Use beautiful Kotlin code to generate beautiful Kotlin code.
+POM_PACKAGING=jar
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt
new file mode 100644
index 0000000..d44264b
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Array
+import java.util.Objects
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor7
+import kotlin.reflect.KClass
+
+/** A generated annotation on a declaration. */
+public class AnnotationSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+  @Deprecated(
+    message = "Use typeName instead. This property will be removed in KotlinPoet 2.0.",
+    replaceWith = ReplaceWith("typeName"),
+  )
+  public val className: ClassName
+    get() = typeName as? ClassName ?: error("ClassName is not available. Call typeName instead.")
+  public val typeName: TypeName = builder.typeName
+  public val members: List<CodeBlock> = builder.members.toImmutableList()
+  public val useSiteTarget: UseSiteTarget? = builder.useSiteTarget
+
+  internal fun emit(codeWriter: CodeWriter, inline: Boolean, asParameter: Boolean = false) {
+    if (!asParameter) {
+      codeWriter.emit("@")
+    }
+    if (useSiteTarget != null) {
+      codeWriter.emit(useSiteTarget.keyword + ":")
+    }
+    codeWriter.emitCode("%T", typeName)
+
+    if (members.isEmpty() && !asParameter) {
+      // @Singleton
+      return
+    }
+
+    val whitespace = if (inline) "" else "\n"
+    val memberSeparator = if (inline) ", " else ",\n"
+    val memberSuffix = if (!inline && members.size > 1) "," else ""
+
+    // Inline:
+    //   @Column(name = "updated_at", nullable = false)
+    //
+    // Not inline:
+    //   @Column(
+    //       name = "updated_at",
+    //       nullable = false,
+    //   )
+
+    codeWriter.emit("(")
+    if (members.size > 1) codeWriter.emit(whitespace).indent(1)
+    codeWriter.emitCode(
+      codeBlock = members
+        .map { if (inline) it.replaceAll("[⇥|⇤]", "") else it }
+        .joinToCode(separator = memberSeparator, suffix = memberSuffix),
+      isConstantContext = true,
+    )
+    if (members.size > 1) codeWriter.unindent(1).emit(whitespace)
+    codeWriter.emit(")")
+  }
+
+  public fun toBuilder(): Builder {
+    val builder = Builder(typeName)
+    builder.members += members
+    builder.useSiteTarget = useSiteTarget
+    builder.tags += tagMap.tags
+    return builder
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString {
+    emit(this, inline = true, asParameter = false)
+  }
+
+  public enum class UseSiteTarget(internal val keyword: String) {
+    FILE("file"),
+    PROPERTY("property"),
+    FIELD("field"),
+    GET("get"),
+    SET("set"),
+    RECEIVER("receiver"),
+    PARAM("param"),
+    SETPARAM("setparam"),
+    DELEGATE("delegate"),
+  }
+
+  public class Builder internal constructor(
+    internal val typeName: TypeName,
+  ) : Taggable.Builder<Builder> {
+    internal var useSiteTarget: UseSiteTarget? = null
+
+    public val members: MutableList<CodeBlock> = mutableListOf()
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+    public fun addMember(format: String, vararg args: Any): Builder =
+      addMember(CodeBlock.of(format, *args))
+
+    public fun addMember(codeBlock: CodeBlock): Builder = apply {
+      members += codeBlock
+    }
+
+    public fun useSiteTarget(useSiteTarget: UseSiteTarget?): Builder = apply {
+      this.useSiteTarget = useSiteTarget
+    }
+
+    public fun build(): AnnotationSpec = AnnotationSpec(this)
+
+    public companion object {
+      /**
+       * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
+       * Handles a number of special cases, such as appending "f" to `Float` values, and uses
+       * `%L` for other types.
+       */
+      internal fun memberForValue(value: Any) = when (value) {
+        is Class<*> -> CodeBlock.of("%T::class", value)
+        is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
+        is String -> CodeBlock.of("%S", value)
+        is Float -> CodeBlock.of("%Lf", value)
+        is Char -> CodeBlock.of("'%L'", characterLiteralWithoutSingleQuotes(value))
+        else -> CodeBlock.of("%L", value)
+      }
+    }
+  }
+
+  /**
+   * Annotation value visitor adding members to the given builder instance.
+   */
+  @OptIn(DelicateKotlinPoetApi::class)
+  private class Visitor(
+    val builder: CodeBlock.Builder,
+  ) : SimpleAnnotationValueVisitor7<CodeBlock.Builder, String>(builder) {
+
+    override fun defaultAction(o: Any, name: String) =
+      builder.add(Builder.memberForValue(o))
+
+    override fun visitAnnotation(a: AnnotationMirror, name: String) =
+      builder.add("%L", get(a))
+
+    override fun visitEnumConstant(c: VariableElement, name: String) =
+      builder.add("%T.%L", c.asType().asTypeName(), c.simpleName)
+
+    override fun visitType(t: TypeMirror, name: String) =
+      builder.add("%T::class", t.asTypeName())
+
+    override fun visitArray(values: List<AnnotationValue>, name: String): CodeBlock.Builder {
+      builder.add("arrayOf(⇥⇥")
+      values.forEachIndexed { index, value ->
+        if (index > 0) builder.add(", ")
+        value.accept(this, name)
+      }
+      builder.add("⇤⇤)")
+      return builder
+    }
+  }
+
+  public companion object {
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    @JvmOverloads
+    public fun get(
+      annotation: Annotation,
+      includeDefaultValues: Boolean = false,
+    ): AnnotationSpec {
+      try {
+        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+        val javaAnnotation = annotation as java.lang.annotation.Annotation
+        val builder = builder(javaAnnotation.annotationType())
+          .tag(annotation)
+        val methods = annotation.annotationType().declaredMethods.sortedBy { it.name }
+        for (method in methods) {
+          val value = method.invoke(annotation)
+          if (!includeDefaultValues) {
+            if (Objects.deepEquals(value, method.defaultValue)) {
+              continue
+            }
+          }
+          val member = CodeBlock.builder()
+          member.add("%L = ", method.name)
+          if (value.javaClass.isArray) {
+            member.add("arrayOf(⇥⇥")
+            for (i in 0 until Array.getLength(value)) {
+              if (i > 0) member.add(", ")
+              member.add(Builder.memberForValue(Array.get(value, i)))
+            }
+            member.add("⇤⇤)")
+            builder.addMember(member.build())
+            continue
+          }
+          if (value is Annotation) {
+            member.add("%L", get(value))
+            builder.addMember(member.build())
+            continue
+          }
+          member.add("%L", Builder.memberForValue(value))
+          builder.addMember(member.build())
+        }
+        return builder.build()
+      } catch (e: Exception) {
+        throw RuntimeException("Reflecting $annotation failed!", e)
+      }
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+        " the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun get(annotation: AnnotationMirror): AnnotationSpec {
+      val element = annotation.annotationType.asElement() as TypeElement
+      val builder = builder(element.asClassName()).tag(annotation)
+      for (executableElement in annotation.elementValues.keys) {
+        val member = CodeBlock.builder()
+        val visitor = Visitor(member)
+        val name = executableElement.simpleName.toString()
+        member.add("%L = ", name)
+        val value = annotation.elementValues[executableElement]!!
+        value.accept(visitor, name)
+        builder.addMember(member.build())
+      }
+      return builder.build()
+    }
+
+    @JvmStatic public fun builder(type: ClassName): Builder = Builder(type)
+
+    @JvmStatic public fun builder(type: ParameterizedTypeName): Builder = Builder(type)
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun builder(type: Class<out Annotation>): Builder =
+      builder(type.asClassName())
+
+    @JvmStatic public fun builder(type: KClass<out Annotation>): Builder =
+      builder(type.asClassName())
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt
new file mode 100644
index 0000000..119d05a
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 Google, 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.
+ */
+@file:JvmName("ClassNames")
+
+package com.squareup.kotlinpoet
+
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.NestingKind.MEMBER
+import javax.lang.model.element.NestingKind.TOP_LEVEL
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+
+/** A fully-qualified class name for top-level and member classes.  */
+public class ClassName internal constructor(
+  names: List<String>,
+  nullable: Boolean = false,
+  annotations: List<AnnotationSpec> = emptyList(),
+  tags: Map<KClass<*>, Any> = emptyMap()
+) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> {
+  /**
+   * Returns a class name created from the given parts. For example, calling this with package name
+   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+   */
+  @Deprecated("", level = DeprecationLevel.HIDDEN)
+  public constructor(packageName: String, simpleName: String, vararg simpleNames: String) :
+    this(listOf(packageName, simpleName, *simpleNames))
+
+  @Deprecated(
+    "Simple names must not be empty. Did you forget an argument?",
+    level = DeprecationLevel.ERROR,
+    replaceWith = ReplaceWith("ClassName(packageName, TODO())"),
+  )
+  public constructor(packageName: String) : this(packageName, listOf())
+
+  /**
+   * Returns a class name created from the given parts. For example, calling this with package name
+   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+   */
+  public constructor(packageName: String, vararg simpleNames: String) :
+    this(listOf(packageName, *simpleNames)) {
+      require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
+      require(simpleNames.none { it.isEmpty() }) {
+        "simpleNames must not contain empty items: ${simpleNames.contentToString()}"
+      }
+    }
+
+  /**
+   * Returns a class name created from the given parts. For example, calling this with package name
+   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+   */
+  public constructor(packageName: String, simpleNames: List<String>) :
+    this(mutableListOf(packageName).apply { addAll(simpleNames) }) {
+      require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
+      require(simpleNames.none { it.isEmpty() }) {
+        "simpleNames must not contain empty items: $simpleNames"
+      }
+    }
+
+  /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */
+  private val names = names.toImmutableList()
+
+  /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */
+  public val canonicalName: String = if (names[0].isEmpty())
+    names.subList(1, names.size).joinToString(".") else
+    names.joinToString(".")
+
+  /** Package name, like `"kotlin.collections"` for `Map.Entry`. */
+  public val packageName: String get() = names[0]
+
+  /** Simple name of this class, like `"Entry"` for `Map.Entry`. */
+  public val simpleName: String get() = names[names.size - 1]
+
+  /**
+   * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]`
+   * for `Map.Entry`.
+   */
+  public val simpleNames: List<String> get() = names.subList(1, names.size)
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>
+  ): ClassName {
+    return ClassName(names, nullable, annotations, tags)
+  }
+
+  /**
+   * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not
+   * nested in another class.
+   */
+  public fun enclosingClassName(): ClassName? {
+    return if (names.size != 2)
+      ClassName(names.subList(0, names.size - 1)) else
+      null
+  }
+
+  /**
+   * Returns the top class in this nesting group. Equivalent to chained calls to
+   * [ClassName.enclosingClassName] until the result's enclosing class is null.
+   */
+  public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2))
+
+  /**
+   * Fully qualified name using `.` to separate package from the top level class name, and `$` to
+   * separate nested classes, like `kotlin.collections.Map$Entry`.
+   */
+  public fun reflectionName(): String {
+    // trivial case: no nested names
+    if (names.size == 2) {
+      return if (packageName.isEmpty())
+        names[1] else
+        packageName + "." + names[1]
+    }
+    // concat top level class name and nested names
+    return buildString {
+      append(topLevelClassName().canonicalName)
+      for (name in simpleNames.subList(1, simpleNames.size)) {
+        append('$').append(name)
+      }
+    }
+  }
+
+  /**
+   * Callable reference to the constructor of this class. Emits the enclosing class if one exists,
+   * followed by the reference operator `::`, followed by either [simpleName] or the
+   * fully-qualified name if this is a top-level class.
+   *
+   * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required
+   * for a top-level class with a conflicting name.
+   */
+  public fun constructorReference(): CodeBlock {
+    val enclosing = enclosingClassName()
+    return if (enclosing != null) {
+      CodeBlock.of("%T::%N", enclosing, simpleName)
+    } else {
+      CodeBlock.of("::%T", this)
+    }
+  }
+
+  /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */
+  public fun nestedClass(name: String): ClassName = ClassName(names + name)
+
+  /**
+   * Returns a class that shares the same enclosing package or class. If this class is enclosed by
+   * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise
+   * it is equivalent to `get(packageName(), name)`.
+   */
+  public fun peerClass(name: String): ClassName {
+    val result = names.toMutableList()
+    result[result.size - 1] = name
+    return ClassName(result)
+  }
+
+  /**
+   * Orders by the fully-qualified name. Nested types are ordered immediately after their
+   * enclosing type. For example, the following types are ordered by this method:
+   *
+   * ```
+   * com.example.Robot
+   * com.example.Robot.Motor
+   * com.example.RoboticVacuum
+   * ```
+   */
+  override fun compareTo(other: ClassName): Int = canonicalName.compareTo(other.canonicalName)
+
+  override fun emit(out: CodeWriter) =
+    out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
+
+  public companion object {
+    /**
+     * Returns a new [ClassName] instance for the given fully-qualified class name string. This
+     * method assumes that the input is ASCII and follows typical Java style (lowercase package
+     * names, UpperCamelCase class names) and may produce incorrect results or throw
+     * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as
+     * it can create [ClassName] instances without such restrictions.
+     */
+    @JvmStatic public fun bestGuess(classNameString: String): ClassName {
+      val names = mutableListOf<String>()
+
+      // Add the package name, like "java.util.concurrent", or "" for no package.
+      var p = 0
+      while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) {
+        p = classNameString.indexOf('.', p) + 1
+        require(p != 0) { "couldn't make a guess for $classNameString" }
+      }
+      names += if (p != 0) classNameString.substring(0, p - 1) else ""
+
+      // Add the class names, like "Map" and "Entry".
+      for (part in classNameString.substring(p).split('.')) {
+        require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) {
+          "couldn't make a guess for $classNameString"
+        }
+
+        names += part
+      }
+
+      require(names.size >= 2) { "couldn't make a guess for $classNameString" }
+      return ClassName(names)
+    }
+  }
+}
+
+@DelicateKotlinPoetApi(
+  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead."
+)
+@JvmName("get")
+public fun Class<*>.asClassName(): ClassName {
+  require(!isPrimitive) { "primitive types cannot be represented as a ClassName" }
+  require(Void.TYPE != this) { "'void' type cannot be represented as a ClassName" }
+  require(!isArray) { "array types cannot be represented as a ClassName" }
+  val names = mutableListOf<String>()
+  var c = this
+  while (true) {
+    names += c.simpleName
+    val enclosing = c.enclosingClass ?: break
+    c = enclosing
+  }
+  // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
+  val lastDot = c.name.lastIndexOf('.')
+  if (lastDot != -1) names += c.name.substring(0, lastDot)
+  names.reverse()
+  return ClassName(names)
+}
+
+@JvmName("get")
+public fun KClass<*>.asClassName(): ClassName {
+  qualifiedName?.let { return ClassName.bestGuess(it) }
+  throw IllegalArgumentException("$this cannot be represented as a ClassName")
+}
+
+/** Returns the class name for `element`. */
+@DelicateKotlinPoetApi(
+  message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead."
+)
+@JvmName("get")
+public fun TypeElement.asClassName(): ClassName {
+  fun isClassOrInterface(e: Element) = e.kind.isClass || e.kind.isInterface
+
+  fun getPackage(type: Element): PackageElement {
+    var t = type
+    while (t.kind != ElementKind.PACKAGE) {
+      t = t.enclosingElement
+    }
+    return t as PackageElement
+  }
+
+  val names = mutableListOf<String>()
+  var e: Element = this
+  while (isClassOrInterface(e)) {
+    val eType = e as TypeElement
+    require(eType.nestingKind.isOneOf(TOP_LEVEL, MEMBER)) {
+      "unexpected type testing"
+    }
+    names += eType.simpleName.toString()
+    e = eType.enclosingElement
+  }
+  names += getPackage(this).qualifiedName.toString()
+  names.reverse()
+  return ClassName(names)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
new file mode 100644
index 0000000..61b4b20
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+@file:JvmName("CodeBlocks")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+/**
+ * A fragment of a .kt file, potentially containing declarations, statements, and documentation.
+ * Code blocks are not necessarily well-formed Kotlin code, and are not validated. This class
+ * assumes kotlinc will check correctness later!
+ *
+ * Code blocks support placeholders like [java.text.Format]. This class primarily uses a percent
+ * sign `%` but has its own set of permitted placeholders:
+ *
+ *  * `%L` emits a *literal* value with no escaping. Arguments for literals may be strings,
+ *    primitives, [type declarations][TypeSpec], [annotations][AnnotationSpec] and even other code
+ *    blocks.
+ *  * `%N` emits a *name*, using name collision avoidance where necessary. Arguments for names may
+ *    be strings (actually any [character sequence][CharSequence]), [parameters][ParameterSpec],
+ *    [properties][PropertySpec], [functions][FunSpec], and [types][TypeSpec].
+ *  * `%S` escapes the value as a *string*, wraps it with double quotes, and emits that. For
+ *    example, `6" sandwich` is emitted `"6\" sandwich"`. `%S` will also escape all dollar signs
+ *    (`$`), use `%P` for string templates.
+ *  * `%P` - Similar to `%S`, but doesn't escape dollar signs (`$`) to allow creation of string
+ *    templates. If the string contains dollar signs that should be escaped - use `%S`.
+ *  * `%T` emits a *type* reference. Types will be imported if possible. Arguments for types may be
+ *    [classes][Class].
+ *  * `%M` emits a *member* reference. A member is either a function or a property. If the member is
+ *    importable, e.g. it's a top-level function or a property declared inside an object, the import
+ *    will be resolved if possible. Arguments for members must be of type [MemberName].
+ *  * `%%` emits a percent sign.
+ *  * `·` emits a space that never wraps. KotlinPoet prefers to wrap lines longer than 100 columns.
+ *    It does this by replacing normal spaces with a newline and indent. Note that spaces in strings
+ *    are never wrapped.
+ *  * `⇥` increases the indentation level.
+ *  * `⇤` decreases the indentation level.
+ *  * `«` begins a statement. For multiline statements, every line after the first line is
+ *    double-indented.
+ *  * `»` ends a statement.
+ */
+public class CodeBlock private constructor(
+  internal val formatParts: List<String>,
+  internal val args: List<Any?>,
+) {
+  /** A heterogeneous list containing string literals and value placeholders.  */
+
+  public fun isEmpty(): Boolean = formatParts.isEmpty()
+
+  public fun isNotEmpty(): Boolean = !isEmpty()
+
+  /**
+   * Returns a code block with `prefix` stripped off, or null if this code block doesn't start with
+   * `prefix`.
+   *
+   * This is a pretty basic implementation that might not cover cases like mismatched whitespace. We
+   * could offer something more lenient if necessary.
+   */
+  internal fun withoutPrefix(prefix: CodeBlock): CodeBlock? {
+    if (formatParts.size < prefix.formatParts.size) return null
+    if (args.size < prefix.args.size) return null
+
+    var prefixArgCount = 0
+    var firstFormatPart: String? = null
+
+    // Walk through the formatParts of prefix to confirm that it's a of this.
+    prefix.formatParts.forEachIndexed { index, formatPart ->
+      if (formatParts[index] != formatPart) {
+        // We've found a format part that doesn't match. If this is the very last format part check
+        // for a string prefix match. If that doesn't match, we're done.
+        if (index == prefix.formatParts.size - 1 && formatParts[index].startsWith(formatPart)) {
+          firstFormatPart = formatParts[index].substring(formatPart.length)
+        } else {
+          return null
+        }
+      }
+
+      // If the matching format part has an argument, check that too.
+      if (formatPart.startsWith("%") && !formatPart[1].isMultiCharNoArgPlaceholder) {
+        if (args[prefixArgCount] != prefix.args[prefixArgCount]) {
+          return null // Argument doesn't match.
+        }
+        prefixArgCount++
+      }
+    }
+
+    // We found a prefix. Prepare the suffix as a result.
+    val resultFormatParts = ArrayList<String>()
+    firstFormatPart?.let {
+      resultFormatParts.add(it)
+    }
+    for (i in prefix.formatParts.size until formatParts.size) {
+      resultFormatParts.add(formatParts[i])
+    }
+
+    val resultArgs = ArrayList<Any?>()
+    for (i in prefix.args.size until args.size) {
+      resultArgs.add(args[i])
+    }
+
+    return CodeBlock(resultFormatParts, resultArgs)
+  }
+
+  /**
+   * Returns a copy of the code block without leading and trailing no-arg placeholders
+   * (`⇥`, `⇤`, `«`, `»`).
+   */
+  internal fun trim(): CodeBlock {
+    var start = 0
+    var end = formatParts.size
+    while (start < end && formatParts[start] in NO_ARG_PLACEHOLDERS) {
+      start++
+    }
+    while (start < end && formatParts[end - 1] in NO_ARG_PLACEHOLDERS) {
+      end--
+    }
+    return when {
+      start > 0 || end < formatParts.size -> CodeBlock(formatParts.subList(start, end), args)
+      else -> this
+    }
+  }
+
+  /**
+   * Returns a copy of the code block with selected format parts replaced, similar to
+   * [java.lang.String.replaceAll].
+   *
+   * **Warning!** This method leaves the arguments list unchanged. Take care when replacing
+   * placeholders with arguments, such as `%L`, as it can result in a code block, where
+   * placeholders don't match their arguments.
+   */
+  internal fun replaceAll(oldValue: String, newValue: String) =
+    CodeBlock(formatParts.map { it.replace(oldValue, newValue) }, args)
+
+  internal fun hasStatements() = formatParts.any { "«" in it }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString { emitCode(this@CodeBlock) }
+
+  internal fun toString(codeWriter: CodeWriter): String = buildCodeString(codeWriter) {
+    emitCode(this@CodeBlock)
+  }
+
+  public fun toBuilder(): Builder {
+    val builder = Builder()
+    builder.formatParts += formatParts
+    builder.args.addAll(args)
+    return builder
+  }
+
+  public class Builder {
+    internal val formatParts = mutableListOf<String>()
+    internal val args = mutableListOf<Any?>()
+
+    public fun isEmpty(): Boolean = formatParts.isEmpty()
+
+    public fun isNotEmpty(): Boolean = !isEmpty()
+
+    /**
+     * Adds code using named arguments.
+     *
+     * Named arguments specify their name after the '%' followed by : and the corresponding type
+     * character. Argument names consist of characters in `a-z, A-Z, 0-9, and _` and must start
+     * with a lowercase character.
+     *
+     * For example, to refer to the type [java.lang.Integer] with the argument name `clazz` use a
+     * format string containing `%clazz:T` and include the key `clazz` with value
+     * `java.lang.Integer.class` in the argument map.
+     */
+    public fun addNamed(format: String, arguments: Map<String, *>): Builder = apply {
+      var p = 0
+
+      for (argument in arguments.keys) {
+        require(LOWERCASE matches argument) {
+          "argument '$argument' must start with a lowercase character"
+        }
+      }
+
+      while (p < format.length) {
+        val nextP = format.nextPotentialPlaceholderPosition(startIndex = p)
+        if (nextP == -1) {
+          formatParts += format.substring(p, format.length)
+          break
+        }
+
+        if (p != nextP) {
+          formatParts += format.substring(p, nextP)
+          p = nextP
+        }
+
+        var matchResult: MatchResult? = null
+        val colon = format.indexOf(':', p)
+        if (colon != -1) {
+          val endIndex = (colon + 2).coerceAtMost(format.length)
+          matchResult = NAMED_ARGUMENT.matchEntire(format.substring(p, endIndex))
+        }
+        if (matchResult != null) {
+          val argumentName = matchResult.groupValues[ARG_NAME]
+          require(arguments.containsKey(argumentName)) {
+            "Missing named argument for %$argumentName"
+          }
+          val formatChar = matchResult.groupValues[TYPE_NAME].first()
+          addArgument(format, formatChar, arguments[argumentName])
+          formatParts += "%$formatChar"
+          p += matchResult.range.last + 1
+        } else if (format[p].isSingleCharNoArgPlaceholder) {
+          formatParts += format.substring(p, p + 1)
+          p++
+        } else {
+          require(p < format.length - 1) { "dangling % at end" }
+          require(format[p + 1].isMultiCharNoArgPlaceholder) {
+            "unknown format %${format[p + 1]} at ${p + 1} in '$format'"
+          }
+          formatParts += format.substring(p, p + 2)
+          p += 2
+        }
+      }
+    }
+
+    /**
+     * Add code with positional or relative arguments.
+     *
+     * Relative arguments map 1:1 with the placeholders in the format string.
+     *
+     * Positional arguments use an index after the placeholder to identify which argument index
+     * to use. For example, for a literal to reference the 3rd argument: "%3L" (1 based index)
+     *
+     * Mixing relative and positional arguments in a call to add is invalid and will result in an
+     * error.
+     */
+    public fun add(format: String, vararg args: Any?): Builder = apply {
+      var hasRelative = false
+      var hasIndexed = false
+
+      var relativeParameterCount = 0
+      val indexedParameterCount = IntArray(args.size)
+
+      var p = 0
+      while (p < format.length) {
+        if (format[p].isSingleCharNoArgPlaceholder) {
+          formatParts += format[p].toString()
+          p++
+          continue
+        }
+
+        if (format[p] != '%') {
+          var nextP = format.nextPotentialPlaceholderPosition(startIndex = p + 1)
+          if (nextP == -1) nextP = format.length
+          formatParts += format.substring(p, nextP)
+          p = nextP
+          continue
+        }
+
+        p++ // '%'.
+
+        // Consume zero or more digits, leaving 'c' as the first non-digit char after the '%'.
+        val indexStart = p
+        var c: Char
+        do {
+          require(p < format.length) { "dangling format characters in '$format'" }
+          c = format[p++]
+        } while (c in '0'..'9')
+        val indexEnd = p - 1
+
+        // If 'c' doesn't take an argument, we're done.
+        if (c.isMultiCharNoArgPlaceholder) {
+          require(indexStart == indexEnd) { "%% may not have an index" }
+          formatParts += "%$c"
+          continue
+        }
+
+        // Find either the indexed argument, or the relative argument. (0-based).
+        val index: Int
+        if (indexStart < indexEnd) {
+          index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1
+          hasIndexed = true
+          if (args.isNotEmpty()) {
+            indexedParameterCount[index % args.size]++ // modulo is needed, checked below anyway
+          }
+        } else {
+          index = relativeParameterCount
+          hasRelative = true
+          relativeParameterCount++
+        }
+
+        require(index >= 0 && index < args.size) {
+          "index ${index + 1} for '${format.substring(
+            indexStart - 1,
+            indexEnd + 1,
+          )}' not in range (received ${args.size} arguments)"
+        }
+        require(!hasIndexed || !hasRelative) { "cannot mix indexed and positional parameters" }
+
+        addArgument(format, c, args[index])
+
+        formatParts += "%$c"
+      }
+
+      if (hasRelative) {
+        require(relativeParameterCount >= args.size) {
+          "unused arguments: expected $relativeParameterCount, received ${args.size}"
+        }
+      }
+      if (hasIndexed) {
+        val unused = mutableListOf<String>()
+        for (i in args.indices) {
+          if (indexedParameterCount[i] == 0) {
+            unused += "%" + (i + 1)
+          }
+        }
+        val s = if (unused.size == 1) "" else "s"
+        require(unused.isEmpty()) { "unused argument$s: ${unused.joinToString(", ")}" }
+      }
+    }
+
+    private fun addArgument(format: String, c: Char, arg: Any?) {
+      when (c) {
+        'N' -> this.args += argToName(arg).escapeIfNecessary()
+        'L' -> this.args += argToLiteral(arg)
+        'S' -> this.args += argToString(arg)
+        'P' -> this.args += if (arg is CodeBlock) arg else argToString(arg)
+        'T' -> this.args += argToType(arg)
+        'M' -> this.args += arg
+        else -> throw IllegalArgumentException(
+          String.format("invalid format string: '%s'", format),
+        )
+      }
+    }
+
+    private fun argToName(o: Any?) = when (o) {
+      is CharSequence -> o.toString()
+      is ParameterSpec -> o.name
+      is PropertySpec -> o.name
+      is FunSpec -> o.name
+      is TypeSpec -> o.name!!
+      is MemberName -> o.simpleName
+      else -> throw IllegalArgumentException("expected name but was $o")
+    }
+
+    private fun argToLiteral(o: Any?) = if (o is Number) formatNumericValue(o) else o
+
+    private fun argToString(o: Any?) = o?.toString()
+
+    private fun formatNumericValue(o: Number): Any? {
+      val format = DecimalFormatSymbols().apply {
+        decimalSeparator = '.'
+        groupingSeparator = '_'
+      }
+
+      val precision = if (o is Float || o is Double) o.toString().split(".").last().length else 0
+
+      val pattern = when (o) {
+        is Float, is Double -> "###,##0.0" + "#".repeat(precision - 1)
+        else -> "###,##0"
+      }
+
+      return DecimalFormat(pattern, format).format(o)
+    }
+
+    private fun logDeprecationWarning(o: Any) {
+      println(
+        "Deprecation warning: converting $o to TypeName. Conversion of TypeMirror and" +
+          " TypeElement is deprecated in KotlinPoet, use kotlin-metadata APIs instead.",
+      )
+    }
+
+    private fun argToType(o: Any?) = when (o) {
+      is TypeName -> o
+      is TypeMirror -> {
+        logDeprecationWarning(o)
+        o.asTypeName()
+      }
+      is Element -> {
+        logDeprecationWarning(o)
+        o.asType().asTypeName()
+      }
+      is Type -> o.asTypeName()
+      is KClass<*> -> o.asTypeName()
+      else -> throw IllegalArgumentException("expected type but was $o")
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as `if (foo == 5)`.
+     *     Shouldn't contain newline characters. Can contain opening braces, e.g.
+     *     `beginControlFlow("list.forEach { element ->")`. If there's no opening brace at the end
+     *     of the string, it will be added.
+     */
+    public fun beginControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
+      add(controlFlow.withOpeningBrace(), *args)
+      indent()
+    }
+
+    private fun String.withOpeningBrace(): String {
+      for (i in length - 1 downTo 0) {
+        if (this[i] == '{') {
+          return "$this\n"
+        } else if (this[i] == '}') {
+          break
+        }
+      }
+      return "$this·{\n"
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+     *     Shouldn't contain braces or newline characters.
+     */
+    public fun nextControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
+      unindent()
+      add("}·$controlFlow·{\n", *args)
+      indent()
+    }
+
+    public fun endControlFlow(): Builder = apply {
+      unindent()
+      add("}\n")
+    }
+
+    public fun addStatement(format: String, vararg args: Any?): Builder = apply {
+      add("«")
+      add(format, *args)
+      add("\n»")
+    }
+
+    public fun add(codeBlock: CodeBlock): Builder = apply {
+      formatParts += codeBlock.formatParts
+      args.addAll(codeBlock.args)
+    }
+
+    public fun indent(): Builder = apply {
+      formatParts += "⇥"
+    }
+
+    public fun unindent(): Builder = apply {
+      formatParts += "⇤"
+    }
+
+    public fun clear(): Builder = apply {
+      formatParts.clear()
+      args.clear()
+    }
+
+    public fun build(): CodeBlock = CodeBlock(formatParts.toImmutableList(), args.toImmutableList())
+  }
+
+  public companion object {
+    private val NAMED_ARGUMENT = Regex("%([\\w_]+):([\\w]).*")
+    private val LOWERCASE = Regex("[a-z]+[\\w_]*")
+    private const val ARG_NAME = 1
+    private const val TYPE_NAME = 2
+    private val NO_ARG_PLACEHOLDERS = setOf("⇥", "⇤", "«", "»")
+    internal val EMPTY = CodeBlock(emptyList(), emptyList())
+
+    @JvmStatic public fun of(format: String, vararg args: Any?): CodeBlock =
+      Builder().add(format, *args).build()
+
+    @JvmStatic public fun builder(): Builder = Builder()
+
+    internal val Char.isMultiCharNoArgPlaceholder get() = this == '%'
+    internal val Char.isSingleCharNoArgPlaceholder get() = isOneOf('⇥', '⇤', '«', '»')
+    internal val String.isPlaceholder
+      get() = (length == 1 && first().isSingleCharNoArgPlaceholder) ||
+        (length == 2 && first().isMultiCharNoArgPlaceholder)
+
+    internal fun String.nextPotentialPlaceholderPosition(startIndex: Int) =
+      indexOfAny(charArrayOf('%', '«', '»', '⇥', '⇤'), startIndex)
+  }
+}
+
+@JvmOverloads
+public fun Collection<CodeBlock>.joinToCode(
+  separator: CharSequence = ", ",
+  prefix: CharSequence = "",
+  suffix: CharSequence = "",
+): CodeBlock {
+  val blocks = toTypedArray()
+  val placeholders = Array(blocks.size) { "%L" }
+  return CodeBlock.of(placeholders.joinToString(separator, prefix, suffix), *blocks)
+}
+
+/**
+ * Builds new [CodeBlock] by populating newly created [CodeBlock.Builder] using provided
+ * [builderAction] and then converting it to [CodeBlock].
+ */
+public inline fun buildCodeBlock(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock {
+  return CodeBlock.builder().apply(builderAction).build()
+}
+
+/**
+ * Calls [CodeBlock.Builder.indent] then executes the provided [builderAction] on the
+ * [CodeBlock.Builder] and then executes [CodeBlock.Builder.unindent] before returning the
+ * original [CodeBlock.Builder].
+ */
+public inline fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
+  return indent().also(builderAction).unindent()
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
new file mode 100644
index 0000000..2884c65
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import java.io.Closeable
+import kotlin.math.min
+
+/** Sentinel value that indicates that no user-provided package has been set.  */
+private val NO_PACKAGE = String()
+
+internal val NULLABLE_ANY = ANY.copy(nullable = true)
+
+private fun extractMemberName(part: String): String {
+  require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
+  for (i in 1..part.length) {
+    if (!part.substring(0, i).isIdentifier) {
+      return part.substring(0, i - 1)
+    }
+  }
+  return part
+}
+
+internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
+  val stringBuilder = StringBuilder()
+  CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
+    it.builderAction()
+  }
+  return stringBuilder.toString()
+}
+
+internal fun buildCodeString(
+  codeWriter: CodeWriter,
+  builderAction: CodeWriter.() -> Unit,
+): String {
+  val stringBuilder = StringBuilder()
+  codeWriter.emitInto(stringBuilder, builderAction)
+  return stringBuilder.toString()
+}
+
+/**
+ * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
+ * imports, indentation, and deferred variable names.
+ */
+internal class CodeWriter constructor(
+  out: Appendable,
+  private val indent: String = DEFAULT_INDENT,
+  imports: Map<String, Import> = emptyMap(),
+  private val importedTypes: Map<String, ClassName> = emptyMap(),
+  private val importedMembers: Map<String, MemberName> = emptyMap(),
+  columnLimit: Int = 100,
+) : Closeable {
+  private var out = LineWrapper(out, indent, columnLimit)
+  private var indentLevel = 0
+
+  private var kdoc = false
+  private var comment = false
+  private var packageName = NO_PACKAGE
+  private val typeSpecStack = mutableListOf<TypeSpec>()
+  private val memberImportNames = mutableSetOf<String>()
+  private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
+  private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
+  private val referencedNames = mutableSetOf<String>()
+  private var trailingNewline = false
+
+  val imports = imports.also {
+    for ((memberName, _) in imports) {
+      val lastDotIndex = memberName.lastIndexOf('.')
+      if (lastDotIndex >= 0) {
+        memberImportNames.add(memberName.substring(0, lastDotIndex))
+      }
+    }
+  }
+
+  /**
+   * When emitting a statement, this is the line of the statement currently being written. The first
+   * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
+   * is -1 when the currently-written line isn't part of a statement.
+   */
+  var statementLine = -1
+
+  fun indent(levels: Int = 1) = apply {
+    indentLevel += levels
+  }
+
+  fun unindent(levels: Int = 1) = apply {
+    require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
+    indentLevel -= levels
+  }
+
+  fun pushPackage(packageName: String) = apply {
+    check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
+    this.packageName = packageName
+  }
+
+  fun popPackage() = apply {
+    check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
+    packageName = NO_PACKAGE
+  }
+
+  fun pushType(type: TypeSpec) = apply {
+    this.typeSpecStack.add(type)
+  }
+
+  fun popType() = apply {
+    this.typeSpecStack.removeAt(typeSpecStack.size - 1)
+  }
+
+  fun emitComment(codeBlock: CodeBlock) {
+    trailingNewline = true // Force the '//' prefix for the comment.
+    comment = true
+    try {
+      emitCode(codeBlock)
+      emit("\n")
+    } finally {
+      comment = false
+    }
+  }
+
+  fun emitKdoc(kdocCodeBlock: CodeBlock) {
+    if (kdocCodeBlock.isEmpty()) return
+
+    emit("/**\n")
+    kdoc = true
+    try {
+      emitCode(kdocCodeBlock, ensureTrailingNewline = true)
+    } finally {
+      kdoc = false
+    }
+    emit(" */\n")
+  }
+
+  fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
+    for (annotationSpec in annotations) {
+      annotationSpec.emit(this, inline)
+      emit(if (inline) " " else "\n")
+    }
+  }
+
+  /**
+   * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
+   * be emitted except for [KModifier.PUBLIC]
+   */
+  fun emitModifiers(
+    modifiers: Set<KModifier>,
+    implicitModifiers: Set<KModifier> = emptySet(),
+  ) {
+    if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
+      emit(KModifier.PUBLIC.keyword)
+      emit(" ")
+    }
+    val uniqueNonPublicExplicitOnlyModifiers =
+      modifiers
+        .filterNot { it == KModifier.PUBLIC }
+        .filterNot { implicitModifiers.contains(it) }
+        .toEnumSet()
+    for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
+      emit(modifier.keyword)
+      emit(" ")
+    }
+  }
+
+  /**
+   * Emits the `context` block for [contextReceivers].
+   */
+  fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
+    if (contextReceivers.isNotEmpty()) {
+      val receivers = contextReceivers
+        .map { CodeBlock.of("%T", it) }
+        .joinToCode(prefix = "context(", suffix = ")")
+      emitCode(receivers)
+      emit(suffix)
+    }
+  }
+
+  /**
+   * Emit type variables with their bounds. If a type variable has more than a single bound - call
+   * [emitWhereBlock] with same input to produce an additional `where` block.
+   *
+   * This should only be used when declaring type variables; everywhere else bounds are omitted.
+   */
+  fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
+    if (typeVariables.isEmpty()) return
+
+    emit("<")
+    typeVariables.forEachIndexed { index, typeVariable ->
+      if (index > 0) emit(", ")
+      if (typeVariable.variance != null) {
+        emit("${typeVariable.variance.keyword} ")
+      }
+      if (typeVariable.isReified) {
+        emit("reified ")
+      }
+      emitCode("%L", typeVariable.name)
+      if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
+        emitCode(" : %T", typeVariable.bounds[0])
+      }
+    }
+    emit(">")
+  }
+
+  /**
+   * Emit a `where` block containing type bounds for each type variable that has at least two
+   * bounds.
+   */
+  fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
+    if (typeVariables.isEmpty()) return
+
+    var firstBound = true
+    for (typeVariable in typeVariables) {
+      if (typeVariable.bounds.size > 1) {
+        for (bound in typeVariable.bounds) {
+          if (!firstBound) emitCode(", ") else emitCode(" where ")
+          emitCode("%L : %T", typeVariable.name, bound)
+          firstBound = false
+        }
+      }
+    }
+  }
+
+  fun emitCode(s: String) = emitCode(CodeBlock.of(s))
+
+  fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))
+
+  fun emitCode(
+    codeBlock: CodeBlock,
+    isConstantContext: Boolean = false,
+    ensureTrailingNewline: Boolean = false,
+  ) = apply {
+    var a = 0
+    var deferredTypeName: ClassName? = null // used by "import static" logic
+    val partIterator = codeBlock.formatParts.listIterator()
+    while (partIterator.hasNext()) {
+      when (val part = partIterator.next()) {
+        "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)
+
+        "%N" -> emit(codeBlock.args[a++] as String)
+
+        "%S" -> {
+          val string = codeBlock.args[a++] as String?
+          // Emit null as a literal null: no quotes.
+          val literal = if (string != null) {
+            stringLiteralWithQuotes(
+              string,
+              isInsideRawString = false,
+              isConstantContext = isConstantContext,
+            )
+          } else {
+            "null"
+          }
+          emit(literal, nonWrapping = true)
+        }
+
+        "%P" -> {
+          val string = codeBlock.args[a++]?.let { arg ->
+            if (arg is CodeBlock) {
+              arg.toString(this@CodeWriter)
+            } else {
+              arg as String?
+            }
+          }
+          // Emit null as a literal null: no quotes.
+          val literal = if (string != null) {
+            stringLiteralWithQuotes(
+              string,
+              isInsideRawString = true,
+              isConstantContext = isConstantContext,
+            )
+          } else {
+            "null"
+          }
+          emit(literal, nonWrapping = true)
+        }
+
+        "%T" -> {
+          var typeName = codeBlock.args[a++] as TypeName
+          if (typeName.isAnnotated) {
+            typeName.emitAnnotations(this)
+            typeName = typeName.copy(annotations = emptyList())
+          }
+          // defer "typeName.emit(this)" if next format part will be handled by the default case
+          var defer = false
+          if (typeName is ClassName && partIterator.hasNext()) {
+            if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
+              val candidate = typeName
+              if (candidate.canonicalName in memberImportNames) {
+                check(deferredTypeName == null) { "pending type for static import?!" }
+                deferredTypeName = candidate
+                defer = true
+              }
+            }
+          }
+          if (!defer) typeName.emit(this)
+          typeName.emitNullable(this)
+        }
+
+        "%M" -> {
+          val memberName = codeBlock.args[a++] as MemberName
+          memberName.emit(this)
+        }
+
+        "%%" -> emit("%")
+
+        "⇥" -> indent()
+
+        "⇤" -> unindent()
+
+        "«" -> {
+          check(statementLine == -1) {
+            """
+            |Can't open a new statement until the current statement is closed (opening « followed
+            |by another « without a closing »).
+            |Current code block:
+            |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
+            |- Arguments: ${codeBlock.args}
+            |
+            """.trimMargin()
+          }
+          statementLine = 0
+        }
+
+        "»" -> {
+          check(statementLine != -1) {
+            """
+            |Can't close a statement that hasn't been opened (closing » is not preceded by an
+            |opening «).
+            |Current code block:
+            |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
+            |- Arguments: ${codeBlock.args}
+            |
+            """.trimMargin()
+          }
+          if (statementLine > 0) {
+            unindent(2) // End a multi-line statement. Decrease the indentation level.
+          }
+          statementLine = -1
+        }
+
+        else -> {
+          // Handle deferred type.
+          var doBreak = false
+          if (deferredTypeName != null) {
+            if (part.startsWith(".")) {
+              if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
+                // Okay, static import hit and all was emitted, so clean-up and jump to next part.
+                deferredTypeName = null
+                doBreak = true
+              }
+            }
+            if (!doBreak) {
+              deferredTypeName!!.emit(this)
+              deferredTypeName = null
+            }
+          }
+          if (!doBreak) {
+            emit(part)
+          }
+        }
+      }
+    }
+    if (ensureTrailingNewline && out.hasPendingSegments) {
+      emit("\n")
+    }
+  }
+
+  private fun emitStaticImportMember(canonical: String, part: String): Boolean {
+    val partWithoutLeadingDot = part.substring(1)
+    if (partWithoutLeadingDot.isEmpty()) return false
+    val first = partWithoutLeadingDot[0]
+    if (!Character.isJavaIdentifierStart(first)) return false
+    val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
+    if (explicit != null) {
+      if (explicit.alias != null) {
+        val memberName = extractMemberName(partWithoutLeadingDot)
+        emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
+      } else {
+        emit(partWithoutLeadingDot)
+      }
+      return true
+    }
+    return false
+  }
+
+  private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
+    when (o) {
+      is TypeSpec -> o.emit(this, null)
+      is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
+      is PropertySpec -> o.emit(this, emptySet())
+      is FunSpec -> o.emit(
+        codeWriter = this,
+        enclosingName = null,
+        implicitModifiers = setOf(KModifier.PUBLIC),
+        includeKdocTags = true,
+      )
+      is TypeAliasSpec -> o.emit(this)
+      is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
+      else -> emit(o.toString())
+    }
+  }
+
+  /**
+   * Returns the best name to identify `className` with in the current context. This uses the
+   * available imports and the current scope to find the shortest name available. It does not honor
+   * names visible due to inheritance.
+   */
+  fun lookupName(className: ClassName): String {
+    // Find the shortest suffix of className that resolves to className. This uses both local type
+    // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
+    var nameResolved = false
+    var c: ClassName? = className
+    while (c != null) {
+      val alias = imports[c.canonicalName]?.alias
+      val simpleName = alias ?: c.simpleName
+      val resolved = resolve(simpleName)
+      nameResolved = resolved != null
+
+      // We don't care about nullability and type annotations here, as it's irrelevant for imports.
+      if (resolved == c.copy(nullable = false, annotations = emptyList())) {
+        if (alias != null) return alias
+        val suffixOffset = c.simpleNames.size - 1
+        referencedNames.add(className.topLevelClassName().simpleName)
+        return className.simpleNames.subList(
+          suffixOffset,
+          className.simpleNames.size,
+        ).joinToString(".")
+      }
+      c = c.enclosingClassName()
+    }
+
+    // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
+    if (nameResolved) {
+      return className.canonicalName
+    }
+
+    // If the class is in the same package, we're done.
+    if (packageName == className.packageName) {
+      referencedNames.add(className.topLevelClassName().simpleName)
+      return className.simpleNames.joinToString(".")
+    }
+
+    // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
+    if (!kdoc) {
+      importableType(className)
+    }
+
+    return className.canonicalName
+  }
+
+  fun lookupName(memberName: MemberName): String {
+    val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
+    // Match an imported member.
+    val importedMember = importedMembers[simpleName]
+    if (importedMember == memberName) {
+      return simpleName
+    } else if (importedMember != null && memberName.enclosingClassName != null) {
+      val enclosingClassName = lookupName(memberName.enclosingClassName)
+      return "$enclosingClassName.$simpleName"
+    }
+
+    // If the member is in the same package, we're done.
+    if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
+      referencedNames.add(memberName.simpleName)
+      return memberName.simpleName
+    }
+
+    // We'll have to use the fully-qualified name.
+    // Mark the member as importable for a future pass unless the name clashes with
+    // a method in the current context
+    if (!kdoc && (
+        memberName.isExtension ||
+          !isMethodNameUsedInCurrentContext(memberName.simpleName)
+        )
+    ) {
+      importableMember(memberName)
+    }
+
+    return memberName.canonicalName
+  }
+
+  // TODO(luqasn): also honor superclass members when resolving names.
+  private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
+    for (it in typeSpecStack.reversed()) {
+      if (it.funSpecs.any { it.name == simpleName }) {
+        return true
+      }
+      if (!it.modifiers.contains(KModifier.INNER)) {
+        break
+      }
+    }
+    return false
+  }
+
+  private fun importableType(className: ClassName) {
+    val topLevelClassName = className.topLevelClassName()
+    val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
+    // Check for name clashes with members.
+    if (simpleName !in importableMembers) {
+      importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
+    }
+  }
+
+  private fun importableMember(memberName: MemberName) {
+    if (memberName.packageName.isNotEmpty()) {
+      val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
+      // Check for name clashes with types.
+      if (simpleName !in importableTypes) {
+        importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
+      }
+    }
+  }
+
+  /**
+   * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
+   * imports.
+   */
+  // TODO(jwilson): also honor superclass members when resolving names.
+  private fun resolve(simpleName: String): ClassName? {
+    // Match a child of the current (potentially nested) class.
+    for (i in typeSpecStack.indices.reversed()) {
+      val typeSpec = typeSpecStack[i]
+      if (simpleName in typeSpec.nestedTypesSimpleNames) {
+        return stackClassName(i, simpleName)
+      }
+    }
+
+    if (typeSpecStack.size > 0) {
+      val typeSpec = typeSpecStack[0]
+      if (typeSpec.name == simpleName) {
+        // Match the top-level class.
+        return ClassName(packageName, simpleName)
+      }
+      if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
+        // Match a top level enum value.
+        // Enum values are not proper classes but can still be modeled using ClassName.
+        return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
+      }
+    }
+
+    // Match an imported type.
+    val importedType = importedTypes[simpleName]
+    if (importedType != null) return importedType
+
+    // No match.
+    return null
+  }
+
+  /** Returns the class named `simpleName` when nested in the class at `stackDepth`.  */
+  private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
+    var className = ClassName(packageName, typeSpecStack[0].name!!)
+    for (i in 1..stackDepth) {
+      className = className.nestedClass(typeSpecStack[i].name!!)
+    }
+    return className.nestedClass(simpleName)
+  }
+
+  /**
+   * Emits `s` with indentation as required. It's important that all code that writes to
+   * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
+   * unnecessary trailing whitespace.
+   */
+  fun emit(s: String, nonWrapping: Boolean = false) = apply {
+    var first = true
+    for (line in s.split('\n')) {
+      // Emit a newline character. Make sure blank lines in KDoc & comments look good.
+      if (!first) {
+        if ((kdoc || comment) && trailingNewline) {
+          emitIndentation()
+          out.appendNonWrapping(if (kdoc) " *" else "//")
+        }
+        out.newline()
+        trailingNewline = true
+        if (statementLine != -1) {
+          if (statementLine == 0) {
+            indent(2) // Begin multiple-line statement. Increase the indentation level.
+          }
+          statementLine++
+        }
+      }
+
+      first = false
+      if (line.isEmpty()) continue // Don't indent empty lines.
+
+      // Emit indentation and comment prefix if necessary.
+      if (trailingNewline) {
+        emitIndentation()
+        if (kdoc) {
+          out.appendNonWrapping(" * ")
+        } else if (comment) {
+          out.appendNonWrapping("// ")
+        }
+      }
+
+      if (nonWrapping) {
+        out.appendNonWrapping(line)
+      } else {
+        out.append(
+          line,
+          indentLevel = if (kdoc) indentLevel else indentLevel + 2,
+          linePrefix = if (kdoc) " * " else "",
+        )
+      }
+      trailingNewline = false
+    }
+  }
+
+  private fun emitIndentation() {
+    for (j in 0 until indentLevel) {
+      out.appendNonWrapping(indent)
+    }
+  }
+
+  /**
+   * Returns whether a [KModifier.PUBLIC] should be emitted.
+   *
+   * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
+   *
+   * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
+   * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
+   * consumer in [modifiers].
+   */
+  private fun shouldEmitPublicModifier(
+    modifiers: Set<KModifier>,
+    implicitModifiers: Set<KModifier>,
+  ): Boolean {
+    if (modifiers.contains(KModifier.PUBLIC)) {
+      return true
+    }
+
+    if (!implicitModifiers.contains(KModifier.PUBLIC)) {
+      return false
+    }
+
+    val hasOtherConsumerSpecifiedVisibility =
+      modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
+
+    return !hasOtherConsumerSpecifiedVisibility
+  }
+
+  /**
+   * Returns the types that should have been imported for this code. If there were any simple name
+   * collisions, import aliases will be generated.
+   */
+  private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
+    return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
+  }
+
+  /**
+   * Returns the members that should have been imported for this code. If there were any simple name
+   * collisions, import aliases will be generated.
+   */
+  private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
+    return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
+  }
+
+  /**
+   * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
+   * [CodeWriter] will continue using the old [Appendable] after this method returns.
+   */
+  inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
+    val codeWrapper = this
+    LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
+      val oldOut = codeWrapper.out
+      codeWrapper.out = newOut
+      action()
+      codeWrapper.out = oldOut
+    }
+  }
+
+  override fun close() {
+    out.close()
+  }
+
+  companion object {
+    /**
+     * Makes a pass to collect imports by executing [emitStep], and returns an instance of
+     * [CodeWriter] pre-initialized with collected imports.
+     */
+    fun withCollectedImports(
+      out: Appendable,
+      indent: String,
+      memberImports: Map<String, Import>,
+      emitStep: (importsCollector: CodeWriter) -> Unit,
+    ): CodeWriter {
+      // First pass: emit the entire class, just to collect the types we'll need to import.
+      val importsCollector = CodeWriter(
+        NullAppendable,
+        indent,
+        memberImports,
+        columnLimit = Integer.MAX_VALUE,
+      )
+      emitStep(importsCollector)
+      val generatedImports = mutableMapOf<String, Import>()
+      val suggestedTypeImports = importsCollector.suggestedTypeImports()
+        .generateImports(
+          generatedImports,
+          canonicalName = ClassName::canonicalName,
+          packageName = ClassName::packageName,
+          capitalizeAliases = true,
+        )
+      val suggestedMemberImports = importsCollector.suggestedMemberImports()
+        .generateImports(
+          generatedImports,
+          canonicalName = MemberName::canonicalName,
+          packageName = MemberName::packageName,
+          capitalizeAliases = false,
+        )
+      importsCollector.close()
+
+      return CodeWriter(
+        out,
+        indent,
+        memberImports + generatedImports.filterKeys { it !in memberImports },
+        suggestedTypeImports,
+        suggestedMemberImports,
+      )
+    }
+
+    private fun <T> Map<String, Set<T>>.generateImports(
+      generatedImports: MutableMap<String, Import>,
+      canonicalName: T.() -> String,
+      packageName: T.() -> String,
+      capitalizeAliases: Boolean,
+    ): Map<String, T> {
+      return flatMap { (simpleName, qualifiedNames) ->
+        if (qualifiedNames.size == 1) {
+          listOf(simpleName to qualifiedNames.first()).also {
+            val canonicalName = qualifiedNames.first().canonicalName()
+            generatedImports[canonicalName] = Import(canonicalName)
+          }
+        } else {
+          generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
+            .onEach { (alias, qualifiedName) ->
+              val canonicalName = qualifiedName.canonicalName()
+              generatedImports[canonicalName] = Import(canonicalName, alias)
+            }
+        }
+      }.toMap()
+    }
+
+    private fun <T> generateImportAliases(
+      simpleName: String,
+      qualifiedNames: Set<T>,
+      packageName: T.() -> String,
+      capitalizeAliases: Boolean,
+    ): List<Pair<String, T>> {
+      val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
+        qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
+      }
+      val aliasNames = mutableMapOf<String, T>()
+      var segmentsToUse = 0
+      // Iterate until we have unique aliases for all names.
+      while (aliasNames.size != qualifiedNames.size) {
+        segmentsToUse += 1
+        aliasNames.clear()
+        for ((qualifiedName, segments) in packageNameSegments) {
+          val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
+            .joinToString(separator = "")
+            .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
+          val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
+          aliasNames[aliasName] = qualifiedName
+        }
+      }
+      return aliasNames.toList()
+    }
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt
new file mode 100644
index 0000000..929096b
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+package com.squareup.kotlinpoet
+
+/** A KotlinPoet spec type that can have a context receiver. */
+public interface ContextReceivable {
+
+  /** The originating elements of this type. */
+  @ExperimentalKotlinPoetApi
+  public val contextReceiverTypes: List<TypeName>
+
+  /** The builder analogue to [ContextReceivable] types. */
+  public interface Builder<out T : Builder<T>> {
+
+    /** Mutable map of the current originating elements this builder contains. */
+    @ExperimentalKotlinPoetApi
+    public val contextReceiverTypes: MutableList<TypeName>
+
+    /** Adds the given [receiverTypes] to this type's list of originating elements. */
+    @Suppress("UNCHECKED_CAST")
+    @ExperimentalKotlinPoetApi
+    public fun contextReceivers(receiverTypes: Iterable<TypeName>): T = apply {
+      contextReceiverTypes += receiverTypes
+    } as T
+
+    /** Adds the given [receiverTypes] to this type's list of originating elements. */
+    @ExperimentalKotlinPoetApi
+    public fun contextReceivers(vararg receiverTypes: TypeName): T =
+      contextReceivers(receiverTypes.toList())
+  }
+}
+
+@ExperimentalKotlinPoetApi
+internal fun ContextReceivable.Builder<*>.buildContextReceivers() =
+  ContextReceivers(contextReceiverTypes.toImmutableList())
+
+@JvmInline
+@ExperimentalKotlinPoetApi
+internal value class ContextReceivers(
+  override val contextReceiverTypes: List<TypeName>,
+) : ContextReceivable
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt
new file mode 100644
index 0000000..1136933
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+/**
+ * Marks declarations in the KotlinPoet API that are **delicate** &mdash;
+ * they have limited use-case and shall be used with care in general code.
+ * Any use of a delicate declaration has to be carefully reviewed to make sure it is
+ * properly used and does not create problems like lossy Java -> Kotlin type parsing.
+ * Carefully read documentation and [message] of any declaration marked as `DelicateKotlinPoetApi`.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@RequiresOptIn(
+  level = RequiresOptIn.Level.WARNING,
+  message = "This is a delicate API and its use requires care." +
+    " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.",
+)
+public annotation class DelicateKotlinPoetApi(val message: String)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt
new file mode 100644
index 0000000..413b7b7
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+public object Dynamic : TypeName(false, emptyList(), TagMap(emptyMap())) {
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>,
+  ): Nothing = throw UnsupportedOperationException("dynamic doesn't support copying")
+
+  override fun emit(out: CodeWriter) = out.apply {
+    emit("dynamic")
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt
new file mode 100644
index 0000000..2e58556
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+package com.squareup.kotlinpoet
+
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+
+/**
+ * Indicates that a given API is experimental and subject to change.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
+public annotation class ExperimentalKotlinPoetApi
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt
new file mode 100644
index 0000000..b2f2549
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStreamWriter
+import java.net.URI
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.Files
+import java.nio.file.Path
+import javax.annotation.processing.Filer
+import javax.tools.JavaFileObject
+import javax.tools.JavaFileObject.Kind
+import javax.tools.SimpleJavaFileObject
+import javax.tools.StandardLocation
+import kotlin.reflect.KClass
+
+/**
+ * A Kotlin file containing top level objects like classes, objects, functions, properties, and type
+ * aliases.
+ *
+ * Items are output in the following order:
+ * - Comment
+ * - Annotations
+ * - Package
+ * - Imports
+ * - Members
+ */
+public class FileSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+  public val comment: CodeBlock = builder.comment.build()
+  public val packageName: String = builder.packageName
+  public val name: String = builder.name
+  public val members: List<Any> = builder.members.toList()
+  public val defaultImports: Set<String> = builder.defaultImports.toSet()
+  public val body: CodeBlock = builder.body.build()
+  public val isScript: Boolean = builder.isScript
+  private val memberImports = builder.memberImports.associateBy(Import::qualifiedName)
+  private val indent = builder.indent
+  private val extension = if (isScript) "kts" else "kt"
+
+  @Throws(IOException::class)
+  public fun writeTo(out: Appendable) {
+    val codeWriter = CodeWriter.withCollectedImports(
+      out = out,
+      indent = indent,
+      memberImports = memberImports,
+      emitStep = { importsCollector -> emit(importsCollector, collectingImports = true) },
+    )
+    emit(codeWriter, collectingImports = false)
+    codeWriter.close()
+  }
+
+  /** Writes this to `directory` as UTF-8 using the standard directory structure.  */
+  @Throws(IOException::class)
+  public fun writeTo(directory: Path) {
+    require(Files.notExists(directory) || Files.isDirectory(directory)) {
+      "path $directory exists but is not a directory."
+    }
+    var outputDirectory = directory
+    if (packageName.isNotEmpty()) {
+      for (packageComponent in packageName.split('.').dropLastWhile { it.isEmpty() }) {
+        outputDirectory = outputDirectory.resolve(packageComponent)
+      }
+    }
+
+    Files.createDirectories(outputDirectory)
+
+    val outputPath = outputDirectory.resolve("$name.$extension")
+    OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8).use { writer -> writeTo(writer) }
+  }
+
+  /** Writes this to `directory` as UTF-8 using the standard directory structure.  */
+  @Throws(IOException::class)
+  public fun writeTo(directory: File): Unit = writeTo(directory.toPath())
+
+  /** Writes this to `filer`.  */
+  @Throws(IOException::class)
+  public fun writeTo(filer: Filer) {
+    val originatingElements = members.asSequence()
+      .filterIsInstance<OriginatingElementsHolder>()
+      .flatMap { it.originatingElements.asSequence() }
+      .toSet()
+    val filerSourceFile = filer.createResource(
+      StandardLocation.SOURCE_OUTPUT,
+      packageName,
+      "$name.$extension",
+      *originatingElements.toTypedArray(),
+    )
+    try {
+      filerSourceFile.openWriter().use { writer -> writeTo(writer) }
+    } catch (e: Exception) {
+      try {
+        filerSourceFile.delete()
+      } catch (ignored: Exception) {
+      }
+      throw e
+    }
+  }
+
+  private fun emit(codeWriter: CodeWriter, collectingImports: Boolean) {
+    if (comment.isNotEmpty()) {
+      codeWriter.emitComment(comment)
+    }
+
+    if (annotations.isNotEmpty()) {
+      codeWriter.emitAnnotations(annotations, inline = false)
+      codeWriter.emit("\n")
+    }
+
+    codeWriter.pushPackage(packageName)
+
+    val escapedPackageName = packageName.escapeSegmentsIfNecessary()
+
+    if (escapedPackageName.isNotEmpty()) {
+      codeWriter.emitCode("package·%L\n", escapedPackageName)
+      codeWriter.emit("\n")
+    }
+
+    // If we don't have default imports or are collecting them, we don't need to filter
+    var isDefaultImport: (String) -> Boolean = { false }
+    if (!collectingImports && defaultImports.isNotEmpty()) {
+      val defaultImports = defaultImports.map(String::escapeSegmentsIfNecessary)
+      isDefaultImport = { importName ->
+        importName.substringBeforeLast(".") in defaultImports
+      }
+    }
+    // Aliased imports should always appear at the bottom of the imports list.
+    val (aliasedImports, nonAliasedImports) = codeWriter.imports.values
+      .partition { it.alias != null }
+    val imports = nonAliasedImports.asSequence().map { it.toString() }
+      .filterNot(isDefaultImport)
+      .toSortedSet()
+      .plus(aliasedImports.map { it.toString() }.toSortedSet())
+
+    if (imports.isNotEmpty()) {
+      for (import in imports) {
+        codeWriter.emitCode("import·%L", import)
+        codeWriter.emit("\n")
+      }
+      codeWriter.emit("\n")
+    }
+
+    if (isScript) {
+      codeWriter.emitCode(body)
+    } else {
+      members.forEachIndexed { index, member ->
+        if (index > 0) codeWriter.emit("\n")
+        when (member) {
+          is TypeSpec -> member.emit(codeWriter, null)
+          is FunSpec -> member.emit(codeWriter, null, setOf(KModifier.PUBLIC), true)
+          is PropertySpec -> member.emit(codeWriter, setOf(KModifier.PUBLIC))
+          is TypeAliasSpec -> member.emit(codeWriter)
+          else -> throw AssertionError()
+        }
+      }
+    }
+
+    codeWriter.popPackage()
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildString { writeTo(this) }
+
+  public fun toJavaFileObject(): JavaFileObject {
+    val uri = URI.create(
+      if (packageName.isEmpty()) {
+        name
+      } else {
+        packageName.replace('.', '/') + '/' + name
+      } + ".$extension",
+    )
+    return object : SimpleJavaFileObject(uri, Kind.SOURCE) {
+      private val lastModified = System.currentTimeMillis()
+      override fun getCharContent(ignoreEncodingErrors: Boolean): String {
+        return this@FileSpec.toString()
+      }
+
+      override fun openInputStream(): InputStream {
+        return ByteArrayInputStream(getCharContent(true).toByteArray(UTF_8))
+      }
+
+      override fun getLastModified() = lastModified
+    }
+  }
+
+  @JvmOverloads
+  public fun toBuilder(packageName: String = this.packageName, name: String = this.name): Builder {
+    val builder = Builder(packageName, name, isScript)
+    builder.annotations.addAll(annotations)
+    builder.comment.add(comment)
+    builder.members.addAll(this.members)
+    builder.indent = indent
+    builder.memberImports.addAll(memberImports.values)
+    builder.defaultImports.addAll(defaultImports)
+    builder.tags += tagMap.tags
+    builder.body.add(body)
+    return builder
+  }
+
+  public class Builder internal constructor(
+    public val packageName: String,
+    public val name: String,
+    public val isScript: Boolean,
+  ) : Taggable.Builder<Builder> {
+    internal val comment = CodeBlock.builder()
+    internal val memberImports = sortedSetOf<Import>()
+    internal var indent = DEFAULT_INDENT
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+    public val defaultImports: MutableSet<String> = mutableSetOf()
+    public val imports: List<Import> get() = memberImports.toList()
+    public val members: MutableList<Any> = mutableListOf()
+    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+    internal val body = CodeBlock.builder()
+
+    /**
+     * Add an annotation to the file.
+     *
+     * The annotation must either have a [`file` use-site target][AnnotationSpec.UseSiteTarget.FILE]
+     * or not have a use-site target specified (in which case it will be changed to `file`).
+     */
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      val spec = when (annotationSpec.useSiteTarget) {
+        FILE -> annotationSpec
+        null -> annotationSpec.toBuilder().useSiteTarget(FILE).build()
+        else -> error(
+          "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
+        )
+      }
+      annotations += spec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder =
+      addAnnotation(AnnotationSpec.builder(annotation).build())
+
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    /** Adds a file-site comment. This is prefixed to the start of the file and different from [addBodyComment]. */
+    public fun addFileComment(format: String, vararg args: Any): Builder = apply {
+      comment.add(format.replace(' ', '·'), *args)
+    }
+
+    @Deprecated(
+      "Use addFileComment() instead.",
+      ReplaceWith("addFileComment(format, args)"),
+      DeprecationLevel.ERROR,
+    )
+    public fun addComment(format: String, vararg args: Any): Builder = addFileComment(format, *args)
+
+    public fun clearComment(): Builder = apply {
+      comment.clear()
+    }
+
+    public fun addType(typeSpec: TypeSpec): Builder = apply {
+      if (isScript) {
+        body.add("%L", typeSpec)
+      } else {
+        members += typeSpec
+      }
+    }
+
+    public fun addFunction(funSpec: FunSpec): Builder = apply {
+      require(!funSpec.isConstructor && !funSpec.isAccessor) {
+        "cannot add ${funSpec.name} to file $name"
+      }
+      if (isScript) {
+        body.add("%L", funSpec)
+      } else {
+        members += funSpec
+      }
+    }
+
+    public fun addProperty(propertySpec: PropertySpec): Builder = apply {
+      if (isScript) {
+        body.add("%L", propertySpec)
+      } else {
+        members += propertySpec
+      }
+    }
+
+    public fun addTypeAlias(typeAliasSpec: TypeAliasSpec): Builder = apply {
+      if (isScript) {
+        body.add("%L", typeAliasSpec)
+      } else {
+        members += typeAliasSpec
+      }
+    }
+
+    public fun addImport(constant: Enum<*>): Builder = addImport(
+      (constant as java.lang.Enum<*>).declaringClass.asClassName(),
+      constant.name,
+    )
+
+    public fun addImport(`class`: Class<*>, vararg names: String): Builder = apply {
+      require(names.isNotEmpty()) { "names array is empty" }
+      addImport(`class`.asClassName(), names.toList())
+    }
+
+    public fun addImport(`class`: KClass<*>, vararg names: String): Builder = apply {
+      require(names.isNotEmpty()) { "names array is empty" }
+      addImport(`class`.asClassName(), names.toList())
+    }
+
+    public fun addImport(className: ClassName, vararg names: String): Builder = apply {
+      require(names.isNotEmpty()) { "names array is empty" }
+      addImport(className, names.toList())
+    }
+
+    public fun addImport(`class`: Class<*>, names: Iterable<String>): Builder =
+      addImport(`class`.asClassName(), names)
+
+    public fun addImport(`class`: KClass<*>, names: Iterable<String>): Builder =
+      addImport(`class`.asClassName(), names)
+
+    public fun addImport(className: ClassName, names: Iterable<String>): Builder = apply {
+      require("*" !in names) { "Wildcard imports are not allowed" }
+      for (name in names) {
+        memberImports += Import(className.canonicalName + "." + name)
+      }
+    }
+
+    public fun addImport(packageName: String, vararg names: String): Builder = apply {
+      require(names.isNotEmpty()) { "names array is empty" }
+      addImport(packageName, names.toList())
+    }
+
+    public fun addImport(packageName: String, names: Iterable<String>): Builder = apply {
+      require("*" !in names) { "Wildcard imports are not allowed" }
+      for (name in names) {
+        memberImports += if (packageName.isNotEmpty()) {
+          Import("$packageName.$name")
+        } else {
+          Import(name)
+        }
+      }
+    }
+
+    public fun addImport(import: Import): Builder = apply {
+      memberImports += import
+    }
+
+    public fun clearImports(): Builder = apply {
+      memberImports.clear()
+    }
+
+    public fun addAliasedImport(`class`: Class<*>, `as`: String): Builder =
+      addAliasedImport(`class`.asClassName(), `as`)
+
+    public fun addAliasedImport(`class`: KClass<*>, `as`: String): Builder =
+      addAliasedImport(`class`.asClassName(), `as`)
+
+    public fun addAliasedImport(className: ClassName, `as`: String): Builder = apply {
+      memberImports += Import(className.canonicalName, `as`)
+    }
+
+    public fun addAliasedImport(
+      className: ClassName,
+      memberName: String,
+      `as`: String,
+    ): Builder = apply {
+      memberImports += Import("${className.canonicalName}.$memberName", `as`)
+    }
+
+    public fun addAliasedImport(memberName: MemberName, `as`: String): Builder = apply {
+      memberImports += Import(memberName.canonicalName, `as`)
+    }
+
+    /**
+     * Adds a default import for the given [packageName].
+     *
+     * The format of this should be the qualified name of the package, e.g. `kotlin`, `java.lang`,
+     * `org.gradle.api`, etc.
+     */
+    public fun addDefaultPackageImport(packageName: String): Builder = apply {
+      defaultImports += packageName
+    }
+
+    /**
+     * Adds Kotlin's standard default package imports as described
+     * [here](https://kotlinlang.org/docs/packages.html#default-imports).
+     */
+    public fun addKotlinDefaultImports(
+      includeJvm: Boolean = true,
+      includeJs: Boolean = true,
+    ): Builder = apply {
+      defaultImports += KOTLIN_DEFAULT_IMPORTS
+      if (includeJvm) {
+        defaultImports += KOTLIN_DEFAULT_JVM_IMPORTS
+      }
+      if (includeJs) {
+        defaultImports += KOTLIN_DEFAULT_JS_IMPORTS
+      }
+    }
+
+    public fun indent(indent: String): Builder = apply {
+      this.indent = indent
+    }
+
+    public fun addCode(format: String, vararg args: Any?): Builder = apply {
+      check(isScript) {
+        "addCode() is only allowed in script files"
+      }
+      body.add(format, *args)
+    }
+
+    public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
+      check(isScript) {
+        "addNamedCode() is only allowed in script files"
+      }
+      body.addNamed(format, args)
+    }
+
+    public fun addCode(codeBlock: CodeBlock): Builder = apply {
+      check(isScript) {
+        "addCode() is only allowed in script files"
+      }
+      body.add(codeBlock)
+    }
+
+    /** Adds a comment to the body of this script file in the order that it was added. */
+    public fun addBodyComment(format: String, vararg args: Any): Builder = apply {
+      check(isScript) {
+        "addBodyComment() is only allowed in script files"
+      }
+      body.add("//·${format.replace(' ', '·')}\n", *args)
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+     * Shouldn't contain braces or newline characters.
+     */
+    public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+      check(isScript) {
+        "beginControlFlow() is only allowed in script files"
+      }
+      body.beginControlFlow(controlFlow, *args)
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+     * Shouldn't contain braces or newline characters.
+     */
+    public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+      check(isScript) {
+        "nextControlFlow() is only allowed in script files"
+      }
+      body.nextControlFlow(controlFlow, *args)
+    }
+
+    public fun endControlFlow(): Builder = apply {
+      check(isScript) {
+        "endControlFlow() is only allowed in script files"
+      }
+      body.endControlFlow()
+    }
+
+    public fun addStatement(format: String, vararg args: Any): Builder = apply {
+      check(isScript) {
+        "addStatement() is only allowed in script files"
+      }
+      body.addStatement(format, *args)
+    }
+
+    public fun clearBody(): Builder = apply {
+      check(isScript) {
+        "clearBody() is only allowed in script files"
+      }
+      body.clear()
+    }
+
+    public fun build(): FileSpec {
+      for (annotationSpec in annotations) {
+        if (annotationSpec.useSiteTarget != FILE) {
+          error(
+            "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
+          )
+        }
+      }
+      return FileSpec(this)
+    }
+  }
+
+  public companion object {
+    @JvmStatic public fun get(packageName: String, typeSpec: TypeSpec): FileSpec {
+      val fileName = typeSpec.name
+        ?: throw IllegalArgumentException("file name required but type has no name")
+      return builder(packageName, fileName).addType(typeSpec).build()
+    }
+
+    @JvmStatic public fun builder(className: ClassName): Builder {
+      require(className.simpleNames.size == 1) {
+        "nested types can't be used to name a file: ${className.simpleNames.joinToString(".")}"
+      }
+      return builder(className.packageName, className.simpleName)
+    }
+
+    @JvmStatic public fun builder(packageName: String, fileName: String): Builder =
+      Builder(packageName, fileName, isScript = false)
+
+    @JvmStatic public fun scriptBuilder(fileName: String, packageName: String = ""): Builder =
+      Builder(packageName, fileName, isScript = true)
+  }
+}
+
+internal const val DEFAULT_INDENT = "  "
+
+private val KOTLIN_DEFAULT_IMPORTS = setOf(
+  "kotlin",
+  "kotlin.annotation",
+  "kotlin.collections",
+  "kotlin.comparisons",
+  "kotlin.io",
+  "kotlin.ranges",
+  "kotlin.sequences",
+  "kotlin.text",
+)
+private val KOTLIN_DEFAULT_JVM_IMPORTS = setOf("java.lang")
+private val KOTLIN_DEFAULT_JS_IMPORTS = setOf("kotlin.js")
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt
new file mode 100644
index 0000000..2aa75ff
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import java.lang.reflect.Type
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.util.Types
+import kotlin.DeprecationLevel.WARNING
+import kotlin.reflect.KClass
+
+/** A generated function declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class FunSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+  private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
+  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
+  public val name: String = builder.name
+  public val kdoc: CodeBlock = builder.kdoc.build()
+  public val returnKdoc: CodeBlock = builder.returnKdoc
+  public val receiverKdoc: CodeBlock = builder.receiverKdoc
+  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+  public val receiverType: TypeName? = builder.receiverType
+
+  public val returnType: TypeName? = builder.returnType
+  public val parameters: List<ParameterSpec> = builder.parameters.toImmutableList()
+  public val delegateConstructor: String? = builder.delegateConstructor
+  public val delegateConstructorArguments: List<CodeBlock> =
+    builder.delegateConstructorArguments.toImmutableList()
+  public val body: CodeBlock = builder.body.build()
+  private val isExternalGetter = name == GETTER && builder.modifiers.contains(EXTERNAL)
+  private val isEmptySetter = name == SETTER && parameters.isEmpty()
+
+  init {
+    require(body.isEmpty() || !builder.modifiers.containsAnyOf(ABSTRACT, EXPECT)) {
+      "abstract or expect function ${builder.name} cannot have code"
+    }
+    if (name == GETTER) {
+      require(!isExternalGetter || body.isEmpty()) {
+        "external getter cannot have code"
+      }
+    } else if (name == SETTER) {
+      require(parameters.size <= 1) {
+        "$name can have at most one parameter"
+      }
+      require(parameters.isNotEmpty() || body.isEmpty()) {
+        "parameterless setter cannot have code"
+      }
+    }
+    require(INLINE in modifiers || typeVariables.none { it.isReified }) {
+      "only type parameters of inline functions can be reified!"
+    }
+  }
+
+  internal fun parameter(name: String) = parameters.firstOrNull { it.name == name }
+
+  internal fun emit(
+    codeWriter: CodeWriter,
+    enclosingName: String?,
+    implicitModifiers: Set<KModifier>,
+    includeKdocTags: Boolean = false,
+  ) {
+    if (includeKdocTags) {
+      codeWriter.emitKdoc(kdocWithTags())
+    } else {
+      codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+    }
+    codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+    codeWriter.emitAnnotations(annotations, false)
+    codeWriter.emitModifiers(modifiers, implicitModifiers)
+
+    if (!isConstructor && !name.isAccessor) {
+      codeWriter.emitCode("fun·")
+    }
+
+    if (typeVariables.isNotEmpty()) {
+      codeWriter.emitTypeVariables(typeVariables)
+      codeWriter.emit(" ")
+    }
+    emitSignature(codeWriter, enclosingName)
+    codeWriter.emitWhereBlock(typeVariables)
+
+    if (shouldOmitBody(implicitModifiers)) {
+      codeWriter.emit("\n")
+      return
+    }
+
+    val asExpressionBody = body.asExpressionBody()
+
+    if (asExpressionBody != null) {
+      codeWriter.emitCode(CodeBlock.of(" = %L", asExpressionBody), ensureTrailingNewline = true)
+    } else if (!isEmptySetter) {
+      codeWriter.emitCode("·{\n")
+      codeWriter.indent()
+      codeWriter.emitCode(body.returnsWithoutLinebreak(), ensureTrailingNewline = true)
+      codeWriter.unindent()
+      codeWriter.emit("}\n")
+    } else {
+      codeWriter.emit("\n")
+    }
+  }
+
+  private fun shouldOmitBody(implicitModifiers: Set<KModifier>): Boolean {
+    if (canNotHaveBody(implicitModifiers)) {
+      check(body.isEmpty()) { "function $name cannot have code" }
+      return true
+    }
+    return canBodyBeOmitted(implicitModifiers) && body.isEmpty()
+  }
+
+  private fun canNotHaveBody(implicitModifiers: Set<KModifier>) =
+    ABSTRACT in modifiers || EXPECT in modifiers + implicitModifiers
+
+  private fun canBodyBeOmitted(implicitModifiers: Set<KModifier>) = isConstructor ||
+    EXTERNAL in (modifiers + implicitModifiers) ||
+    ABSTRACT in modifiers
+
+  private fun emitSignature(codeWriter: CodeWriter, enclosingName: String?) {
+    if (isConstructor) {
+      codeWriter.emitCode("constructor", enclosingName)
+    } else if (name == GETTER) {
+      codeWriter.emitCode("get")
+    } else if (name == SETTER) {
+      codeWriter.emitCode("set")
+    } else {
+      if (receiverType != null) {
+        if (receiverType is LambdaTypeName) {
+          codeWriter.emitCode("(%T).", receiverType)
+        } else {
+          codeWriter.emitCode("%T.", receiverType)
+        }
+      }
+      codeWriter.emitCode("%N", this)
+    }
+
+    if (!isEmptySetter && !isExternalGetter) {
+      parameters.emit(codeWriter) { param ->
+        param.emit(codeWriter, includeType = name != SETTER)
+      }
+    }
+
+    if (returnType != null) {
+      codeWriter.emitCode(": %T", returnType)
+    } else if (emitUnitReturnType()) {
+      codeWriter.emitCode(": %T", UNIT)
+    }
+
+    if (delegateConstructor != null) {
+      codeWriter.emitCode(
+        delegateConstructorArguments
+          .joinToCode(prefix = " : $delegateConstructor(", suffix = ")"),
+      )
+    }
+  }
+
+  public val isConstructor: Boolean get() = name.isConstructor
+
+  public val isAccessor: Boolean get() = name.isAccessor
+
+  private fun kdocWithTags(): CodeBlock {
+    return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
+      var newLineAdded = false
+      val isNotEmpty = isNotEmpty()
+      if (receiverKdoc.isNotEmpty()) {
+        if (isNotEmpty) {
+          add("\n")
+          newLineAdded = true
+        }
+        add("@receiver %L", receiverKdoc.ensureEndsWithNewLine())
+      }
+      parameters.forEachIndexed { index, parameterSpec ->
+        if (parameterSpec.kdoc.isNotEmpty()) {
+          if (!newLineAdded && index == 0 && isNotEmpty) {
+            add("\n")
+            newLineAdded = true
+          }
+          add("@param %L %L", parameterSpec.name, parameterSpec.kdoc.ensureEndsWithNewLine())
+        }
+      }
+      if (returnKdoc.isNotEmpty()) {
+        if (!newLineAdded && isNotEmpty) {
+          add("\n")
+          newLineAdded = true
+        }
+        add("@return %L", returnKdoc.ensureEndsWithNewLine())
+      }
+      build()
+    }
+  }
+
+  /**
+   * Returns whether [Unit] should be emitted as the return type.
+   *
+   * [Unit] is emitted as return type on a function unless:
+   *   - It's a constructor
+   *   - It's a getter/setter on a property
+   *   - It's an expression body
+   */
+  private fun emitUnitReturnType(): Boolean {
+    if (isConstructor) {
+      return false
+    }
+    if (name == GETTER || name == SETTER) {
+      // Getter/setters don't emit return types
+      return false
+    }
+
+    return body.asExpressionBody() == null
+  }
+
+  private fun CodeBlock.asExpressionBody(): CodeBlock? {
+    val codeBlock = this.trim()
+    val asReturnExpressionBody = codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_SPACE)
+      ?: codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_NBSP)
+    if (asReturnExpressionBody != null) {
+      return asReturnExpressionBody
+    }
+    if (codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_SPACE) != null ||
+      codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_NBSP) != null
+    ) {
+      return codeBlock
+    }
+    return null
+  }
+
+  private fun CodeBlock.returnsWithoutLinebreak(): CodeBlock {
+    val returnWithSpace = RETURN_EXPRESSION_BODY_PREFIX_SPACE.formatParts[0]
+    val returnWithNbsp = RETURN_EXPRESSION_BODY_PREFIX_NBSP.formatParts[0]
+    var originCodeBlockBuilder: CodeBlock.Builder? = null
+    for ((i, formatPart) in formatParts.withIndex()) {
+      if (formatPart.startsWith(returnWithSpace)) {
+        val builder = originCodeBlockBuilder ?: toBuilder()
+        originCodeBlockBuilder = builder
+        builder.formatParts[i] = formatPart.replaceFirst(returnWithSpace, returnWithNbsp)
+      }
+    }
+    return originCodeBlockBuilder?.build() ?: this
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString {
+    emit(
+      codeWriter = this,
+      enclosingName = "Constructor",
+      implicitModifiers = TypeSpec.Kind.CLASS.implicitFunctionModifiers(),
+      includeKdocTags = true,
+    )
+  }
+
+  @JvmOverloads
+  public fun toBuilder(name: String = this.name): Builder {
+    val builder = Builder(name)
+    builder.kdoc.add(kdoc)
+    builder.returnKdoc = returnKdoc
+    builder.receiverKdoc = receiverKdoc
+    builder.annotations += annotations
+    builder.modifiers += modifiers
+    builder.typeVariables += typeVariables
+    builder.returnType = returnType
+    builder.parameters += parameters
+    builder.delegateConstructor = delegateConstructor
+    builder.delegateConstructorArguments += delegateConstructorArguments
+    builder.body.add(body)
+    builder.receiverType = receiverType
+    builder.tags += tagMap.tags
+    builder.originatingElements += originatingElements
+    builder.contextReceiverTypes += contextReceiverTypes
+    return builder
+  }
+
+  public class Builder internal constructor(
+    internal val name: String,
+  ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
+    internal val kdoc = CodeBlock.builder()
+    internal var returnKdoc = CodeBlock.EMPTY
+    internal var receiverKdoc = CodeBlock.EMPTY
+    internal var receiverType: TypeName? = null
+    internal var returnType: TypeName? = null
+    internal var delegateConstructor: String? = null
+    internal var delegateConstructorArguments = listOf<CodeBlock>()
+    internal val body = CodeBlock.builder()
+
+    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+    public val modifiers: MutableList<KModifier> = mutableListOf()
+    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+    public val parameters: MutableList<ParameterSpec> = mutableListOf()
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+    override val originatingElements: MutableList<Element> = mutableListOf()
+    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+
+    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+      kdoc.add(format, *args)
+    }
+
+    public fun addKdoc(block: CodeBlock): Builder = apply {
+      kdoc.add(block)
+    }
+
+    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+      this.annotations += annotationSpecs
+    }
+
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      annotations += annotationSpec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder = apply {
+      annotations += AnnotationSpec.builder(annotation).build()
+    }
+
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    public fun jvmModifiers(modifiers: Iterable<Modifier>) {
+      var visibility = KModifier.INTERNAL
+      for (modifier in modifiers) {
+        when (modifier) {
+          Modifier.PUBLIC -> visibility = KModifier.PUBLIC
+          Modifier.PROTECTED -> visibility = KModifier.PROTECTED
+          Modifier.PRIVATE -> visibility = KModifier.PRIVATE
+          Modifier.ABSTRACT -> this.modifiers += KModifier.ABSTRACT
+          Modifier.FINAL -> this.modifiers += KModifier.FINAL
+          Modifier.NATIVE -> this.modifiers += KModifier.EXTERNAL
+          Modifier.DEFAULT -> Unit
+          Modifier.STATIC -> addAnnotation(JvmStatic::class)
+          Modifier.SYNCHRONIZED -> addAnnotation(Synchronized::class)
+          Modifier.STRICTFP -> addAnnotation(Strictfp::class)
+          else -> throw IllegalArgumentException("unexpected fun modifier $modifier")
+        }
+      }
+      this.modifiers += visibility
+    }
+
+    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+      this.typeVariables += typeVariables
+    }
+
+    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+      typeVariables += typeVariable
+    }
+
+    @ExperimentalKotlinPoetApi
+    override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
+      check(!name.isConstructor) { "constructors cannot have context receivers" }
+      check(!name.isAccessor) { "$name cannot have context receivers" }
+      contextReceiverTypes += receiverTypes
+    }
+
+    @JvmOverloads public fun receiver(
+      receiverType: TypeName,
+      kdoc: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = apply {
+      check(!name.isConstructor) { "$name cannot have receiver type" }
+      this.receiverType = receiverType
+      this.receiverKdoc = kdoc
+    }
+
+    @JvmOverloads public fun receiver(
+      receiverType: Type,
+      kdoc: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = receiver(receiverType.asTypeName(), kdoc)
+
+    public fun receiver(
+      receiverType: Type,
+      kdoc: String,
+      vararg args: Any,
+    ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))
+
+    @JvmOverloads public fun receiver(
+      receiverType: KClass<*>,
+      kdoc: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = receiver(receiverType.asTypeName(), kdoc)
+
+    public fun receiver(
+      receiverType: KClass<*>,
+      kdoc: String,
+      vararg args: Any,
+    ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))
+
+    @JvmOverloads public fun returns(
+      returnType: TypeName,
+      kdoc: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = apply {
+      check(!name.isConstructor && !name.isAccessor) { "$name cannot have a return type" }
+      this.returnType = returnType
+      this.returnKdoc = kdoc
+    }
+
+    @JvmOverloads public fun returns(returnType: Type, kdoc: CodeBlock = CodeBlock.EMPTY): Builder =
+      returns(returnType.asTypeName(), kdoc)
+
+    public fun returns(returnType: Type, kdoc: String, vararg args: Any): Builder =
+      returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))
+
+    @JvmOverloads public fun returns(
+      returnType: KClass<*>,
+      kdoc: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = returns(returnType.asTypeName(), kdoc)
+
+    public fun returns(returnType: KClass<*>, kdoc: String, vararg args: Any): Builder =
+      returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))
+
+    public fun addParameters(parameterSpecs: Iterable<ParameterSpec>): Builder = apply {
+      for (parameterSpec in parameterSpecs) {
+        addParameter(parameterSpec)
+      }
+    }
+
+    public fun addParameter(parameterSpec: ParameterSpec): Builder = apply {
+      parameters += parameterSpec
+    }
+
+    public fun callThisConstructor(args: List<CodeBlock>): Builder = apply {
+      callConstructor("this", args)
+    }
+
+    public fun callThisConstructor(args: Iterable<CodeBlock>): Builder = apply {
+      callConstructor("this", args.toList())
+    }
+
+    public fun callThisConstructor(vararg args: String): Builder = apply {
+      callConstructor("this", args.map { CodeBlock.of(it) })
+    }
+
+    public fun callThisConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
+      callConstructor("this", args.toList())
+    }
+
+    public fun callSuperConstructor(args: Iterable<CodeBlock>): Builder = apply {
+      callConstructor("super", args.toList())
+    }
+
+    public fun callSuperConstructor(args: List<CodeBlock>): Builder = apply {
+      callConstructor("super", args)
+    }
+
+    public fun callSuperConstructor(vararg args: String): Builder = apply {
+      callConstructor("super", args.map { CodeBlock.of(it) })
+    }
+
+    public fun callSuperConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
+      callConstructor("super", args.toList())
+    }
+
+    private fun callConstructor(constructor: String, args: List<CodeBlock>) {
+      check(name.isConstructor) { "only constructors can delegate to other constructors!" }
+      delegateConstructor = constructor
+      delegateConstructorArguments = args
+    }
+
+    public fun addParameter(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
+      addParameter(ParameterSpec.builder(name, type, *modifiers).build())
+
+    public fun addParameter(name: String, type: Type, vararg modifiers: KModifier): Builder =
+      addParameter(name, type.asTypeName(), *modifiers)
+
+    public fun addParameter(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
+      addParameter(name, type.asTypeName(), *modifiers)
+
+    public fun addParameter(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
+      addParameter(ParameterSpec.builder(name, type, modifiers).build())
+
+    public fun addParameter(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
+      addParameter(name, type.asTypeName(), modifiers)
+
+    public fun addParameter(
+      name: String,
+      type: KClass<*>,
+      modifiers: Iterable<KModifier>,
+    ): Builder = addParameter(name, type.asTypeName(), modifiers)
+
+    public fun addCode(format: String, vararg args: Any?): Builder = apply {
+      body.add(format, *args)
+    }
+
+    public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
+      body.addNamed(format, args)
+    }
+
+    public fun addCode(codeBlock: CodeBlock): Builder = apply {
+      body.add(codeBlock)
+    }
+
+    public fun addComment(format: String, vararg args: Any): Builder = apply {
+      body.add("//·${format.replace(' ', '·')}\n", *args)
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+     * * Shouldn't contain braces or newline characters.
+     */
+    public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+      body.beginControlFlow(controlFlow, *args)
+    }
+
+    /**
+     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+     * *     Shouldn't contain braces or newline characters.
+     */
+    public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+      body.nextControlFlow(controlFlow, *args)
+    }
+
+    public fun endControlFlow(): Builder = apply {
+      body.endControlFlow()
+    }
+
+    public fun addStatement(format: String, vararg args: Any): Builder = apply {
+      body.addStatement(format, *args)
+    }
+
+    public fun clearBody(): Builder = apply {
+      body.clear()
+    }
+
+    public fun build(): FunSpec {
+      check(typeVariables.isEmpty() || !name.isAccessor) { "$name cannot have type variables" }
+      check(!(name == GETTER && parameters.isNotEmpty())) { "$name cannot have parameters" }
+      check(!(name == SETTER && parameters.size > 1)) { "$name can have at most one parameter" }
+      return FunSpec(this)
+    }
+  }
+
+  public companion object {
+    private const val CONSTRUCTOR = "constructor()"
+    internal const val GETTER = "get()"
+    internal const val SETTER = "set()"
+
+    internal val String.isConstructor get() = this == CONSTRUCTOR
+    internal val String.isAccessor get() = this.isOneOf(GETTER, SETTER)
+
+    private val RETURN_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("return ")
+    private val RETURN_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("return·")
+    private val THROW_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("throw ")
+    private val THROW_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("throw·")
+
+    @JvmStatic public fun builder(name: String): Builder = Builder(name)
+
+    @JvmStatic public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR)
+
+    @JvmStatic public fun getterBuilder(): Builder = Builder(GETTER)
+
+    @JvmStatic public fun setterBuilder(): Builder = Builder(SETTER)
+
+    @DelicateKotlinPoetApi(
+      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+        " the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun overriding(method: ExecutableElement): Builder {
+      var modifiers: Set<Modifier> = method.modifiers
+      require(
+        Modifier.PRIVATE !in modifiers &&
+          Modifier.FINAL !in modifiers &&
+          Modifier.STATIC !in modifiers,
+      ) {
+        "cannot override method with modifiers: $modifiers"
+      }
+
+      val methodName = method.simpleName.toString()
+      val funBuilder = builder(methodName)
+
+      funBuilder.addModifiers(KModifier.OVERRIDE)
+
+      modifiers = modifiers.toMutableSet()
+      modifiers.remove(Modifier.ABSTRACT)
+      funBuilder.jvmModifiers(modifiers)
+
+      method.typeParameters
+        .map { it.asType() as TypeVariable }
+        .map { it.asTypeVariableName() }
+        .forEach { funBuilder.addTypeVariable(it) }
+
+      funBuilder.returns(method.returnType.asTypeName())
+      funBuilder.addParameters(ParameterSpec.parametersOf(method))
+      if (method.isVarArgs) {
+        funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
+          .toBuilder()
+          .addModifiers(VARARG)
+          .build()
+      }
+
+      if (method.thrownTypes.isNotEmpty()) {
+        val throwsValueString = method.thrownTypes.joinToString { "%T::class" }
+        funBuilder.addAnnotation(
+          AnnotationSpec.builder(Throws::class)
+            .addMember(throwsValueString, *method.thrownTypes.toTypedArray())
+            .build(),
+        )
+      }
+
+      return funBuilder
+    }
+
+    @Deprecated(
+      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+        " the kotlinpoet-metadata APIs instead.",
+      level = WARNING,
+    )
+    @JvmStatic
+    public fun overriding(
+      method: ExecutableElement,
+      enclosing: DeclaredType,
+      types: Types,
+    ): Builder {
+      val executableType = types.asMemberOf(enclosing, method) as ExecutableType
+      val resolvedParameterTypes = executableType.parameterTypes
+      val resolvedReturnType = executableType.returnType
+
+      val builder = overriding(method)
+      builder.returns(resolvedReturnType.asTypeName())
+      var i = 0
+      val size = builder.parameters.size
+      while (i < size) {
+        val parameter = builder.parameters[i]
+        val type = resolvedParameterTypes[i].asTypeName()
+        builder.parameters[i] = parameter.toBuilder(parameter.name, type).build()
+        i++
+      }
+
+      return builder
+    }
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt
new file mode 100644
index 0000000..59b6b12
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+public data class Import internal constructor(
+  val qualifiedName: String,
+  val alias: String? = null,
+) : Comparable<Import> {
+
+  private val importString = buildString {
+    append(qualifiedName.escapeSegmentsIfNecessary())
+    if (alias != null) {
+      append("·as·${alias.escapeIfNecessary()}")
+    }
+  }
+
+  override fun toString(): String = importString
+
+  override fun compareTo(other: Import): Int = importString.compareTo(other.importString)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt
new file mode 100644
index 0000000..81d0357
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import java.util.EnumSet
+
+public enum class KModifier(
+  internal val keyword: String,
+  private vararg val targets: Target,
+) {
+  // Modifier order defined here:
+  // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
+
+  // Access.
+  PUBLIC("public", Target.PROPERTY),
+  PROTECTED("protected", Target.PROPERTY),
+  PRIVATE("private", Target.PROPERTY),
+  INTERNAL("internal", Target.PROPERTY),
+
+  // Multiplatform modules.
+  EXPECT("expect", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+  ACTUAL("actual", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+
+  FINAL("final", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+  OPEN("open", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+  ABSTRACT("abstract", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+  SEALED("sealed", Target.CLASS),
+  CONST("const", Target.PROPERTY),
+
+  EXTERNAL("external", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+  OVERRIDE("override", Target.FUNCTION, Target.PROPERTY),
+  LATEINIT("lateinit", Target.PROPERTY),
+  TAILREC("tailrec", Target.FUNCTION),
+  VARARG("vararg", Target.PARAMETER),
+  SUSPEND("suspend", Target.FUNCTION),
+  INNER("inner", Target.CLASS),
+
+  ENUM("enum", Target.CLASS),
+  ANNOTATION("annotation", Target.CLASS),
+  VALUE("value", Target.CLASS),
+  FUN("fun", Target.INTERFACE),
+
+  COMPANION("companion", Target.CLASS),
+
+  // Call-site compiler tips.
+  INLINE("inline", Target.FUNCTION),
+  NOINLINE("noinline", Target.PARAMETER),
+  CROSSINLINE("crossinline", Target.PARAMETER),
+  REIFIED("reified", Target.TYPE_PARAMETER),
+
+  INFIX("infix", Target.FUNCTION),
+  OPERATOR("operator", Target.FUNCTION),
+
+  DATA("data", Target.CLASS),
+
+  IN("in", Target.VARIANCE_ANNOTATION),
+  OUT("out", Target.VARIANCE_ANNOTATION),
+  ;
+
+  internal enum class Target {
+    CLASS,
+    VARIANCE_ANNOTATION,
+    PARAMETER,
+    TYPE_PARAMETER,
+    FUNCTION,
+    PROPERTY,
+    INTERFACE,
+  }
+
+  internal fun checkTarget(target: Target) {
+    require(target in targets) { "unexpected modifier $this for $target" }
+  }
+}
+
+internal val VISIBILITY_MODIFIERS: Set<KModifier> = EnumSet.of(PUBLIC, INTERNAL, PROTECTED, PRIVATE)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt
new file mode 100644
index 0000000..461a39f
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+package com.squareup.kotlinpoet
+
+public enum class KOperator(
+  internal val operator: String,
+  internal val functionName: String,
+) {
+  UNARY_PLUS("+", "unaryPlus"),
+  PLUS("+", "plus"),
+  UNARY_MINUS("-", "unaryMinus"),
+  MINUS("-", "minus"),
+  TIMES("*", "times"),
+  DIV("/", "div"),
+  REM("%", "rem"),
+  PLUS_ASSIGN("+=", "plusAssign"),
+  MINUS_ASSIGN("-=", "minusAssign"),
+  TIMES_ASSIGN("*=", "timesAssign"),
+  DIV_ASSIGN("/=", "divAssign"),
+  REM_ASSIGN("%=", "remAssign"),
+  INC("++", "inc"),
+  DEC("--", "dec"),
+  EQUALS("==", "equals"),
+  NOT_EQUALS("!=", "equals"),
+  NOT("!", "not"),
+  RANGE_TO("..", "rangeTo"),
+  CONTAINS("in", "contains"),
+  NOT_CONTAINS("!in", "contains"),
+  GT(">", "compareTo"),
+  LT("<", "compareTo"),
+  GE(">=", "compareTo"),
+  LE("<=", "compareTo"),
+  ITERATOR("in", "iterator"),
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt
new file mode 100644
index 0000000..efb3b83
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class LambdaTypeName private constructor(
+  public val receiver: TypeName? = null,
+  @property:ExperimentalKotlinPoetApi
+  public val contextReceivers: List<TypeName> = emptyList(),
+  parameters: List<ParameterSpec> = emptyList(),
+  public val returnType: TypeName = UNIT,
+  nullable: Boolean = false,
+  public val isSuspending: Boolean = false,
+  annotations: List<AnnotationSpec> = emptyList(),
+  tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+  public val parameters: List<ParameterSpec> = parameters.toImmutableList()
+
+  init {
+    for (param in parameters) {
+      require(param.annotations.isEmpty()) { "Parameters with annotations are not allowed" }
+      require(param.modifiers.isEmpty()) { "Parameters with modifiers are not allowed" }
+      require(param.defaultValue == null) { "Parameters with default values are not allowed" }
+    }
+  }
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>,
+  ): LambdaTypeName {
+    return copy(nullable, annotations, this.isSuspending, tags)
+  }
+
+  public fun copy(
+    nullable: Boolean = this.isNullable,
+    annotations: List<AnnotationSpec> = this.annotations.toList(),
+    suspending: Boolean = this.isSuspending,
+    tags: Map<KClass<*>, Any> = this.tags.toMap(),
+  ): LambdaTypeName {
+    return LambdaTypeName(receiver, contextReceivers, parameters, returnType, nullable, suspending, annotations, tags)
+  }
+
+  override fun emit(out: CodeWriter): CodeWriter {
+    if (isNullable) {
+      out.emit("(")
+    }
+
+    if (isSuspending) {
+      out.emit("suspend·")
+    }
+
+    out.emitContextReceivers(contextReceivers, suffix = "·")
+
+    receiver?.let {
+      if (it.isAnnotated) {
+        out.emitCode("(%T).", it)
+      } else {
+        out.emitCode("%T.", it)
+      }
+    }
+
+    parameters.emit(out)
+    out.emitCode(if (returnType is LambdaTypeName) "·->·(%T)" else "·->·%T", returnType)
+
+    if (isNullable) {
+      out.emit(")")
+    }
+    return out
+  }
+
+  public companion object {
+    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+    @ExperimentalKotlinPoetApi @JvmStatic
+    public fun get(
+      receiver: TypeName? = null,
+      parameters: List<ParameterSpec> = emptyList(),
+      returnType: TypeName,
+      contextReceivers: List<TypeName> = emptyList(),
+    ): LambdaTypeName = LambdaTypeName(receiver, contextReceivers, parameters, returnType)
+
+    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+    @JvmStatic public fun get(
+      receiver: TypeName? = null,
+      parameters: List<ParameterSpec> = emptyList(),
+      returnType: TypeName,
+    ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters, returnType)
+
+    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+    @JvmStatic public fun get(
+      receiver: TypeName? = null,
+      vararg parameters: TypeName = emptyArray(),
+      returnType: TypeName,
+    ): LambdaTypeName {
+      return LambdaTypeName(
+        receiver,
+        emptyList(),
+        parameters.toList().map { ParameterSpec.unnamed(it) },
+        returnType,
+      )
+    }
+
+    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+    @JvmStatic public fun get(
+      receiver: TypeName? = null,
+      vararg parameters: ParameterSpec = emptyArray(),
+      returnType: TypeName,
+    ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters.toList(), returnType)
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt
new file mode 100644
index 0000000..83a155b
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.kotlinpoet
+
+import java.io.Closeable
+
+/**
+ * Implements soft line wrapping on an appendable. To use, append characters using
+ * [LineWrapper.append], which will replace spaces with newlines where necessary. Use
+ * [LineWrapper.appendNonWrapping] to append a string that never wraps.
+ */
+internal class LineWrapper(
+  private val out: Appendable,
+  private val indent: String,
+  private val columnLimit: Int,
+) : Closeable {
+
+  private var closed = false
+
+  /**
+   * Segments of the current line to be joined by spaces or wraps. Never empty, but contains a lone
+   * empty string if no data has been emitted since the last newline.
+   */
+  private val segments = mutableListOf("")
+
+  /** Number of indents in wraps. -1 if the current line has no wraps. */
+  private var indentLevel = -1
+
+  /** Optional prefix that will be prepended to wrapped lines. */
+  private var linePrefix = ""
+
+  /** @return whether or not there are pending segments for the current line. */
+  val hasPendingSegments get() = segments.size != 1 || segments[0].isNotEmpty()
+
+  /** Emit `s` replacing its spaces with line wraps as necessary. */
+  fun append(s: String, indentLevel: Int = -1, linePrefix: String = "") {
+    check(!closed) { "closed" }
+
+    var pos = 0
+    while (pos < s.length) {
+      when (s[pos]) {
+        ' ' -> {
+          // Each space starts a new empty segment.
+          this.indentLevel = indentLevel
+          this.linePrefix = linePrefix
+          segments += ""
+          pos++
+        }
+
+        '\n' -> {
+          // Each newline emits the current segments.
+          newline()
+          pos++
+        }
+
+        '·' -> {
+          // Render · as a non-breaking space.
+          segments[segments.size - 1] += " "
+          pos++
+        }
+
+        else -> {
+          var next = s.indexOfAny(SPECIAL_CHARACTERS, pos)
+          if (next == -1) next = s.length
+          segments[segments.size - 1] += s.substring(pos, next)
+          pos = next
+        }
+      }
+    }
+  }
+
+  /** Emit `s` leaving spaces as-is. */
+  fun appendNonWrapping(s: String) {
+    check(!closed) { "closed" }
+    require(!s.contains("\n"))
+
+    segments[segments.size - 1] += s
+  }
+
+  fun newline() {
+    check(!closed) { "closed" }
+
+    emitCurrentLine()
+    out.append("\n")
+    indentLevel = -1
+  }
+
+  /** Flush any outstanding text and forbid future writes to this line wrapper.  */
+  override fun close() {
+    emitCurrentLine()
+    closed = true
+  }
+
+  private fun emitCurrentLine() {
+    foldUnsafeBreaks()
+
+    var start = 0
+    var columnCount = segments[0].length
+
+    for (i in 1 until segments.size) {
+      val segment = segments[i]
+      val newColumnCount = columnCount + 1 + segment.length
+
+      // If this segment doesn't fit in the current run, print the current run and start a new one.
+      if (newColumnCount > columnLimit) {
+        emitSegmentRange(start, i)
+        start = i
+        columnCount = segment.length + indent.length * indentLevel
+        continue
+      }
+
+      columnCount = newColumnCount
+    }
+
+    // Print the last run.
+    emitSegmentRange(start, segments.size)
+
+    segments.clear()
+    segments += ""
+  }
+
+  private fun emitSegmentRange(startIndex: Int, endIndex: Int) {
+    // If this is a wrapped line we need a newline and an indent.
+    if (startIndex > 0) {
+      out.append("\n")
+      for (i in 0 until indentLevel) {
+        out.append(indent)
+      }
+      out.append(linePrefix)
+    }
+
+    // Emit each segment separated by spaces.
+    out.append(segments[startIndex])
+    for (i in startIndex + 1 until endIndex) {
+      out.append(" ")
+      out.append(segments[i])
+    }
+  }
+
+  /**
+   * Any segment that starts with '+' or '-' can't have a break preceding it. Combine it with the
+   * preceding segment. Note that this doesn't apply to the first segment.
+   */
+  private fun foldUnsafeBreaks() {
+    var i = 1
+    while (i < segments.size) {
+      val segment = segments[i]
+      if (UNSAFE_LINE_START.matches(segment)) {
+        segments[i - 1] = segments[i - 1] + " " + segments[i]
+        segments.removeAt(i)
+        if (i > 1) i--
+      } else {
+        i++
+      }
+    }
+  }
+
+  companion object {
+    private val UNSAFE_LINE_START = Regex("\\s*[-+].*")
+    private val SPECIAL_CHARACTERS = " \n·".toCharArray()
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt
new file mode 100644
index 0000000..c70a10a
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+/**
+ * Represents the name of a member (such as a function or a property).
+ *
+ * @param packageName e.g. `kotlin.collections`
+ * @param enclosingClassName e.g. `Map.Entry.Companion`, if the member is declared inside the
+ * companion object of the Map.Entry class
+ * @param simpleName e.g. `isBlank`, `size`
+ * @param isExtension whether the member is an extension property or an extension function. Default
+ * is false.
+ *
+ * If there is a member with the same name as this member in a local scope, the generated code will
+ * include this member's fully-qualified name to avoid ambiguity, e.g.:
+ *
+ * ```kotlin
+ * package com.squareup.tacos
+ *
+ * import kotlin.Unit
+ *
+ * public class TacoTest {
+ *   public fun test(): Unit {
+ *     kotlin.error("errorText")
+ *   }
+ *
+ *   public fun error(): Unit {
+ *   }
+ * }
+ * ```
+ *
+ * However, since Kotlin compiler does not allow fully-qualified extension members, if [isExtension]
+ * is set to true for this [MemberName], the generated code will include an import for this member
+ * and its simple name at the call site, e.g.:
+ *
+ * ```kotlin
+ * package com.squareup.tacos
+ *
+ * import kotlin.Unit
+ * import kotlin.hashCode
+ *
+ * public class TacoTest {
+ *   public override fun hashCode(): Unit {
+ *     var result = super.hashCode
+ *     if (result == 0) {
+ *       result = result * 37 + embedded_message.hashCode()
+ *       super.hashCode = result
+ *     }
+ *     return result
+ *   }
+ * }
+ * ```
+ */
+public data class MemberName internal constructor(
+  public val packageName: String,
+  public val enclosingClassName: ClassName?,
+  public val simpleName: String,
+  public val operator: KOperator? = null,
+  public val isExtension: Boolean = false,
+) {
+  // TODO(egorand): Reduce the number of overloaded constructors in KotlinPoet 2.0.
+
+  public constructor(
+    packageName: String,
+    simpleName: String,
+  ) : this(packageName, enclosingClassName = null, simpleName)
+
+  public constructor(
+    packageName: String,
+    simpleName: String,
+    isExtension: Boolean,
+  ) : this(packageName, enclosingClassName = null, simpleName, operator = null, isExtension)
+
+  public constructor(
+    enclosingClassName: ClassName,
+    simpleName: String,
+  ) : this(enclosingClassName.packageName, enclosingClassName, simpleName)
+
+  public constructor(
+    enclosingClassName: ClassName,
+    simpleName: String,
+    isExtension: Boolean,
+  ) : this(enclosingClassName.packageName, enclosingClassName, simpleName, operator = null, isExtension)
+
+  public constructor(
+    packageName: String,
+    operator: KOperator,
+  ) : this(packageName, enclosingClassName = null, operator.functionName, operator)
+
+  public constructor(
+    enclosingClassName: ClassName,
+    operator: KOperator,
+  ) : this(enclosingClassName.packageName, enclosingClassName, operator.functionName, operator)
+
+  /** Fully qualified name using `.` as a separator, like `kotlin.String.isBlank`. */
+  public val canonicalName: String = buildString {
+    if (enclosingClassName != null) {
+      append(enclosingClassName.canonicalName)
+      append('.')
+    } else if (packageName.isNotBlank()) {
+      append(packageName)
+      append('.')
+    }
+    append(simpleName)
+  }
+
+  /**
+   * Callable reference to this member. Emits [enclosingClassName] if it exists, followed by
+   * the reference operator `::`, followed by either [simpleName] or the fully-qualified
+   * name if this is a top-level member.
+   *
+   * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be
+   * required for a top-level member with a conflicting name.
+   */
+  public fun reference(): CodeBlock = when (enclosingClassName) {
+    null -> CodeBlock.of("::%M", this)
+    else -> CodeBlock.of("%T::%N", enclosingClassName, simpleName)
+  }
+
+  internal fun emit(out: CodeWriter) {
+    if (operator == null) {
+      out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
+    } else {
+      out.lookupName(this)
+      out.emit(operator.operator)
+    }
+  }
+
+  override fun toString(): String = canonicalName
+
+  public companion object {
+    @Suppress("NOTHING_TO_INLINE")
+    @JvmSynthetic
+    @JvmStatic
+    public inline fun ClassName.member(simpleName: String): MemberName =
+      MemberName(this, simpleName)
+
+    @JvmStatic
+    @JvmName("get")
+    public fun KClass<*>.member(simpleName: String): MemberName =
+      asClassName().member(simpleName)
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    @JvmName("get")
+    public fun Class<*>.member(simpleName: String): MemberName =
+      asClassName().member(simpleName)
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt
new file mode 100644
index 0000000..cda1004
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import java.util.UUID
+
+/**
+ * Assigns Kotlin identifier names to avoid collisions, keywords, and invalid characters. To use,
+ * first create an instance and allocate all of the names that you need. Typically this is a
+ * mix of user-supplied names and constants:
+ *
+ * ```kotlin
+ * val nameAllocator = NameAllocator()
+ * for (property in properties) {
+ *   nameAllocator.newName(property.name, property)
+ * }
+ * nameAllocator.newName("sb", "string builder")
+ * ```
+ *
+ * Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
+ * the allocated name later. Typically the tag is the object that is being named. In the above
+ * example we use `property` for the user-supplied property names, and `"string builder"` for our
+ * constant string builder.
+ *
+ * Once we've allocated names we can use them when generating code:
+ *
+ * ```kotlin
+ * val builder = FunSpec.builder("toString")
+ *     .addModifiers(KModifier.OVERRIDE)
+ *     .returns(String::class)
+ *
+ * builder.addStatement("val %N = %T()",
+ *     nameAllocator.get("string builder"), StringBuilder::class)
+ *
+ * for (property in properties) {
+ *   builder.addStatement("%N.append(%N)",
+ *       nameAllocator.get("string builder"), nameAllocator.get(property))
+ * }
+ * builder.addStatement("return %N.toString()", nameAllocator.get("string builder"))
+ * return builder.build()
+ * ```
+ *
+ * The above code generates unique names if presented with conflicts. Given user-supplied properties
+ * with names `ab` and `sb` this generates the following:
+ *
+ * ```kotlin
+ * override fun toString(): kotlin.String {
+ *   val sb_ = java.lang.StringBuilder()
+ *   sb_.append(ab)
+ *   sb_.append(sb)
+ *   return sb_.toString()
+ * }
+ * ```
+ *
+ * The underscore is appended to `sb` to avoid conflicting with the user-supplied `sb` property.
+ * Underscores are also prefixed for names that start with a digit, and used to replace name-unsafe
+ * characters like space or dash.
+ *
+ * When dealing with multiple independent inner scopes, use a [copy][NameAllocator.copy] of the
+ * NameAllocator used for the outer scope to further refine name allocation for a specific inner
+ * scope.
+ */
+public class NameAllocator private constructor(
+  private val allocatedNames: MutableSet<String>,
+  private val tagToName: MutableMap<Any, String>,
+) {
+  public constructor() : this(mutableSetOf(), mutableMapOf())
+
+  /**
+   * Return a new name using `suggestion` that will not be a Java identifier or clash with other
+   * names. The returned value can be queried multiple times by passing `tag` to
+   * [NameAllocator.get].
+   */
+  @JvmOverloads public fun newName(
+    suggestion: String,
+    tag: Any = UUID.randomUUID().toString(),
+  ): String {
+    var result = toJavaIdentifier(suggestion)
+    while (result.isKeyword || !allocatedNames.add(result)) {
+      result += "_"
+    }
+
+    val replaced = tagToName.put(tag, result)
+    if (replaced != null) {
+      tagToName[tag] = replaced // Put things back as they were!
+      throw IllegalArgumentException("tag $tag cannot be used for both '$replaced' and '$result'")
+    }
+
+    return result
+  }
+
+  /** Retrieve a name created with [NameAllocator.newName]. */
+  public operator fun get(tag: Any): String = requireNotNull(tagToName[tag]) { "unknown tag: $tag" }
+
+  /**
+   * Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
+   * of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
+   * inner code blocks.
+   *
+   * @return A deep copy of this NameAllocator.
+   */
+  public fun copy(): NameAllocator {
+    return NameAllocator(allocatedNames.toMutableSet(), tagToName.toMutableMap())
+  }
+}
+
+private fun toJavaIdentifier(suggestion: String) = buildString {
+  var i = 0
+  while (i < suggestion.length) {
+    val codePoint = suggestion.codePointAt(i)
+    if (i == 0 &&
+      !Character.isJavaIdentifierStart(codePoint) &&
+      Character.isJavaIdentifierPart(codePoint)
+    ) {
+      append("_")
+    }
+
+    val validCodePoint: Int = if (Character.isJavaIdentifierPart(codePoint)) {
+      codePoint
+    } else {
+      '_'.code
+    }
+    appendCodePoint(validCodePoint)
+    i += Character.charCount(codePoint)
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt
new file mode 100644
index 0000000..2700821
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet
+
+import javax.lang.model.element.Element
+
+/** A type that can have originating [elements][Element]. */
+public interface OriginatingElementsHolder {
+
+  /** The originating elements of this type. */
+  public val originatingElements: List<Element>
+
+  /** The builder analogue to [OriginatingElementsHolder] types. */
+  public interface Builder<out T : Builder<T>> {
+
+    /** Mutable map of the current originating elements this builder contains. */
+    public val originatingElements: MutableList<Element>
+
+    /** Adds an [originatingElement] to this type's list of originating elements. */
+    @Suppress("UNCHECKED_CAST")
+    public fun addOriginatingElement(originatingElement: Element): T = apply {
+      originatingElements += originatingElement
+    } as T
+  }
+}
+
+internal fun OriginatingElementsHolder.Builder<*>.buildOriginatingElements() =
+  OriginatingElements(originatingElements.toImmutableList())
+
+internal fun List<Element>.buildOriginatingElements() =
+  OriginatingElements(toImmutableList())
+
+@JvmInline
+internal value class OriginatingElements(
+  override val originatingElements: List<Element>,
+) : OriginatingElementsHolder
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt
new file mode 100644
index 0000000..9c33ecd
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import com.squareup.kotlinpoet.KModifier.NOINLINE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import java.lang.reflect.Type
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.VariableElement
+import kotlin.DeprecationLevel.ERROR
+import kotlin.reflect.KClass
+
+/** A generated parameter declaration. */
+public class ParameterSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+  public val name: String = builder.name
+  public val kdoc: CodeBlock = builder.kdoc.build()
+  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+  public val modifiers: Set<KModifier> = builder.modifiers
+    .also {
+      LinkedHashSet(it).apply {
+        removeAll(ALLOWED_PARAMETER_MODIFIERS)
+        if (!isEmpty()) {
+          throw IllegalArgumentException("Modifiers $this are not allowed on Kotlin parameters. Allowed modifiers: $ALLOWED_PARAMETER_MODIFIERS")
+        }
+      }
+    }
+    .toImmutableSet()
+  public val type: TypeName = builder.type
+  public val defaultValue: CodeBlock? = builder.defaultValue
+
+  public constructor(name: String, type: TypeName, vararg modifiers: KModifier) :
+    this(builder(name, type, *modifiers))
+  public constructor(name: String, type: TypeName, modifiers: Iterable<KModifier>) :
+    this(builder(name, type, modifiers))
+
+  internal fun emit(
+    codeWriter: CodeWriter,
+    includeType: Boolean = true,
+    emitKdoc: Boolean = false,
+    inlineAnnotations: Boolean = true,
+  ) {
+    if (emitKdoc) codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+    codeWriter.emitAnnotations(annotations, inlineAnnotations)
+    codeWriter.emitModifiers(modifiers)
+    if (name.isNotEmpty()) codeWriter.emitCode("%N", this)
+    if (name.isNotEmpty() && includeType) codeWriter.emitCode(":·")
+    if (includeType) codeWriter.emitCode("%T", type)
+    emitDefaultValue(codeWriter)
+  }
+
+  internal fun emitDefaultValue(codeWriter: CodeWriter) {
+    if (defaultValue != null) {
+      codeWriter.emitCode(if (defaultValue.hasStatements()) " = %L" else " = «%L»", defaultValue)
+    }
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString { emit(this) }
+
+  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+    val builder = Builder(name, type)
+    builder.kdoc.add(kdoc)
+    builder.annotations += annotations
+    builder.modifiers += modifiers
+    builder.defaultValue = defaultValue
+    builder.tags += tagMap.tags
+    return builder
+  }
+
+  public class Builder internal constructor(
+    internal val name: String,
+    internal val type: TypeName,
+  ) : Taggable.Builder<Builder> {
+    internal var defaultValue: CodeBlock? = null
+
+    public val kdoc: CodeBlock.Builder = CodeBlock.builder()
+    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+    public val modifiers: MutableList<KModifier> = mutableListOf()
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+      kdoc.add(format, *args)
+    }
+
+    public fun addKdoc(block: CodeBlock): Builder = apply {
+      kdoc.add(block)
+    }
+
+    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+      annotations += annotationSpecs
+    }
+
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      annotations += annotationSpec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder = apply {
+      annotations += AnnotationSpec.builder(annotation).build()
+    }
+
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    @Deprecated(
+      "There are no jvm modifiers applicable to parameters in Kotlin",
+      ReplaceWith(""),
+      level = ERROR,
+    )
+    public fun jvmModifiers(modifiers: Iterable<Modifier>): Builder = apply {
+      throw IllegalArgumentException("JVM modifiers are not permitted on parameters in Kotlin")
+    }
+
+    public fun defaultValue(format: String, vararg args: Any?): Builder =
+      defaultValue(CodeBlock.of(format, *args))
+
+    public fun defaultValue(codeBlock: CodeBlock?): Builder = apply {
+      this.defaultValue = codeBlock
+    }
+
+    public fun build(): ParameterSpec = ParameterSpec(this)
+  }
+
+  public companion object {
+    @DelicateKotlinPoetApi(
+      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+        " the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun get(element: VariableElement): ParameterSpec {
+      val name = element.simpleName.toString()
+      val type = element.asType().asTypeName()
+      return builder(name, type)
+        .build()
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+        " the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun parametersOf(method: ExecutableElement): List<ParameterSpec> =
+      method.parameters.map(::get)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: TypeName,
+      vararg modifiers: KModifier,
+    ): Builder {
+      return Builder(name, type).addModifiers(*modifiers)
+    }
+
+    @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
+      builder(name, type.asTypeName(), *modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: KClass<*>,
+      vararg modifiers: KModifier,
+    ): Builder = builder(name, type.asTypeName(), *modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: TypeName,
+      modifiers: Iterable<KModifier>,
+    ): Builder {
+      return Builder(name, type).addModifiers(modifiers)
+    }
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: Type,
+      modifiers: Iterable<KModifier>,
+    ): Builder = builder(name, type.asTypeName(), modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: KClass<*>,
+      modifiers: Iterable<KModifier>,
+    ): Builder = builder(name, type.asTypeName(), modifiers)
+
+    @JvmStatic public fun unnamed(type: KClass<*>): ParameterSpec = unnamed(type.asTypeName())
+
+    @JvmStatic public fun unnamed(type: Type): ParameterSpec = unnamed(type.asTypeName())
+
+    @JvmStatic public fun unnamed(type: TypeName): ParameterSpec = Builder("", type).build()
+  }
+}
+
+// From https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-parameterModifier
+private val ALLOWED_PARAMETER_MODIFIERS = setOf(VARARG, NOINLINE, CROSSINLINE)
+
+internal fun List<ParameterSpec>.emit(
+  codeWriter: CodeWriter,
+  forceNewLines: Boolean = false,
+  emitBlock: (ParameterSpec) -> Unit = { it.emit(codeWriter) },
+) = with(codeWriter) {
+  emit("(")
+
+  if (isNotEmpty()) {
+    val emitNewLines = size > 2 || forceNewLines
+    if (emitNewLines) {
+      emit("\n")
+      indent(1)
+    }
+    forEachIndexed { index, parameter ->
+      if (index > 0) {
+        emit(if (emitNewLines) "\n" else ", ")
+      }
+      emitBlock(parameter)
+      if (emitNewLines) {
+        emit(",")
+      }
+    }
+    if (emitNewLines) {
+      unindent(1)
+      emit("\n")
+    }
+  }
+  emit(")")
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt
new file mode 100644
index 0000000..ffbcf38
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+@file:JvmName("ParameterizedTypeNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Modifier
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+
+public class ParameterizedTypeName internal constructor(
+  private val enclosingType: TypeName?,
+  public val rawType: ClassName,
+  typeArguments: List<TypeName>,
+  nullable: Boolean = false,
+  annotations: List<AnnotationSpec> = emptyList(),
+  tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+  public val typeArguments: List<TypeName> = typeArguments.toImmutableList()
+
+  init {
+    require(typeArguments.isNotEmpty() || enclosingType != null) {
+      "no type arguments: $rawType"
+    }
+  }
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>,
+  ): ParameterizedTypeName {
+    return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
+  }
+
+  public fun copy(
+    nullable: Boolean = this.isNullable,
+    annotations: List<AnnotationSpec> = this.annotations,
+    tags: Map<KClass<*>, Any> = this.tags,
+    typeArguments: List<TypeName> = this.typeArguments,
+  ): ParameterizedTypeName {
+    return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
+  }
+
+  public fun plusParameter(typeArgument: TypeName): ParameterizedTypeName =
+    ParameterizedTypeName(
+      enclosingType,
+      rawType,
+      typeArguments + typeArgument,
+      isNullable,
+      annotations,
+    )
+
+  public fun plusParameter(typeArgument: KClass<*>): ParameterizedTypeName =
+    plusParameter(typeArgument.asClassName())
+
+  public fun plusParameter(typeArgument: Class<*>): ParameterizedTypeName =
+    plusParameter(typeArgument.asClassName())
+
+  override fun emit(out: CodeWriter): CodeWriter {
+    if (enclosingType != null) {
+      enclosingType.emitAnnotations(out)
+      enclosingType.emit(out)
+      out.emit("." + rawType.simpleName)
+    } else {
+      rawType.emitAnnotations(out)
+      rawType.emit(out)
+    }
+    if (typeArguments.isNotEmpty()) {
+      out.emit("<")
+      typeArguments.forEachIndexed { index, parameter ->
+        if (index > 0) out.emit(",·")
+        parameter.emitAnnotations(out)
+        parameter.emit(out)
+        parameter.emitNullable(out)
+      }
+      out.emit(">")
+    }
+    return out
+  }
+
+  /**
+   * Returns a new [ParameterizedTypeName] instance for the specified `name` as nested inside this
+   * class, with the specified `typeArguments`.
+   */
+  public fun nestedClass(name: String, typeArguments: List<TypeName>): ParameterizedTypeName =
+    ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments)
+
+  public companion object {
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun ClassName.parameterizedBy(
+      vararg typeArguments: TypeName,
+    ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments.toList())
+
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun KClass<*>.parameterizedBy(
+      vararg typeArguments: KClass<*>,
+    ): ParameterizedTypeName =
+      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun Class<*>.parameterizedBy(
+      vararg typeArguments: Type,
+    ): ParameterizedTypeName =
+      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun ClassName.parameterizedBy(
+      typeArguments: List<TypeName>,
+    ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments)
+
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun KClass<*>.parameterizedBy(
+      typeArguments: Iterable<KClass<*>>,
+    ): ParameterizedTypeName =
+      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+    /** Returns a parameterized type, applying `typeArguments` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun Class<*>.parameterizedBy(
+      typeArguments: Iterable<Type>,
+    ): ParameterizedTypeName =
+      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+    /** Returns a parameterized type, applying `typeArgument` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun ClassName.plusParameter(
+      typeArgument: TypeName,
+    ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+    /** Returns a parameterized type, applying `typeArgument` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun KClass<*>.plusParameter(
+      typeArgument: KClass<*>,
+    ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+    /** Returns a parameterized type, applying `typeArgument` to `this`. */
+    @JvmStatic
+    @JvmName("get")
+    public fun Class<*>.plusParameter(
+      typeArgument: Class<*>,
+    ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+    /** Returns a parameterized type equivalent to `type`. */
+    internal fun get(
+      type: ParameterizedType,
+      map: MutableMap<Type, TypeVariableName>,
+    ): ParameterizedTypeName {
+      val rawType = (type.rawType as Class<*>).asClassName()
+      val ownerType = if (type.ownerType is ParameterizedType &&
+        !Modifier.isStatic((type.rawType as Class<*>).modifiers)
+      ) {
+        type.ownerType as ParameterizedType
+      } else {
+        null
+      }
+
+      val typeArguments = type.actualTypeArguments.map { get(it, map = map) }
+      return if (ownerType != null) {
+        get(ownerType, map = map).nestedClass(rawType.simpleName, typeArguments)
+      } else {
+        ParameterizedTypeName(null, rawType, typeArguments)
+      }
+    }
+
+    /** Returns a type name equivalent to type with given list of type arguments. */
+    internal fun get(
+      type: KClass<*>,
+      nullable: Boolean,
+      typeArguments: List<KTypeProjection>,
+    ): TypeName {
+      if (typeArguments.isEmpty()) {
+        return type.asTypeName().run { if (nullable) copy(nullable = true) else this }
+      }
+
+      val effectiveType = if (type.java.isArray) Array<Unit>::class else type
+      val enclosingClass = type.java.enclosingClass?.kotlin
+
+      return ParameterizedTypeName(
+        enclosingClass?.let {
+          get(it, false, typeArguments.drop(effectiveType.typeParameters.size))
+        },
+        effectiveType.asTypeName(),
+        typeArguments.take(effectiveType.typeParameters.size).map { (paramVariance, paramType) ->
+          val typeName = paramType?.asTypeName() ?: return@map STAR
+          when (paramVariance) {
+            null -> STAR
+            KVariance.INVARIANT -> typeName
+            KVariance.IN -> WildcardTypeName.consumerOf(typeName)
+            KVariance.OUT -> WildcardTypeName.producerOf(typeName)
+          }
+        },
+        nullable,
+        effectiveType.annotations.map { AnnotationSpec.get(it) },
+      )
+    }
+  }
+}
+
+/** Returns a parameterized type equivalent to `type`.  */
+@DelicateKotlinPoetApi(
+  message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+    "using the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun ParameterizedType.asParameterizedTypeName(): ParameterizedTypeName =
+  ParameterizedTypeName.get(this, mutableMapOf())
+
+/**
+ * Returns a [TypeName] equivalent to the given Kotlin KType using reflection, maybe using kotlin-reflect
+ * if required.
+ */
+public fun KType.asTypeName(): TypeName {
+  val classifier = this.classifier
+  if (classifier is KTypeParameter) {
+    return classifier.asTypeVariableName().run { if (isMarkedNullable) copy(nullable = true) else this }
+  }
+
+  if (classifier == null || classifier !is KClass<*>) {
+    throw IllegalArgumentException("Cannot build TypeName for $this")
+  }
+
+  return ParameterizedTypeName.get(classifier, this.isMarkedNullable, this.arguments)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
new file mode 100644
index 0000000..8fb5860
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.KModifier.Target.PROPERTY
+import java.lang.reflect.Type
+import java.util.EnumSet
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/** A generated property declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class PropertySpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+  private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
+  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
+  public val mutable: Boolean = builder.mutable
+  public val name: String = builder.name
+  public val type: TypeName = builder.type
+  public val kdoc: CodeBlock = builder.kdoc.build()
+  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+  public val initializer: CodeBlock? = builder.initializer
+  public val delegated: Boolean = builder.delegated
+  public val getter: FunSpec? = builder.getter
+  public val setter: FunSpec? = builder.setter
+  public val receiverType: TypeName? = builder.receiverType
+
+  init {
+    require(
+      typeVariables.none { it.isReified } ||
+        (getter != null || setter != null) &&
+        (getter == null || KModifier.INLINE in getter.modifiers) &&
+        (setter == null || KModifier.INLINE in setter.modifiers),
+    ) {
+      "only type parameters of properties with inline getters and/or setters can be reified!"
+    }
+    require(mutable || setter == null) {
+      "only a mutable property can have a setter"
+    }
+    if (contextReceiverTypes.isNotEmpty()) {
+      requireNotNull(getter) { "properties with context receivers require a $GETTER" }
+      if (mutable) {
+        requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" }
+      }
+    }
+  }
+
+  internal fun emit(
+    codeWriter: CodeWriter,
+    implicitModifiers: Set<KModifier>,
+    withInitializer: Boolean = true,
+    emitKdoc: Boolean = true,
+    inline: Boolean = false,
+    inlineAnnotations: Boolean = inline,
+  ) {
+    val isInlineProperty = getter?.modifiers?.contains(KModifier.INLINE) ?: false &&
+      (!mutable || setter?.modifiers?.contains(KModifier.INLINE) ?: false)
+    val propertyModifiers = if (isInlineProperty) modifiers + KModifier.INLINE else modifiers
+    if (emitKdoc) {
+      codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+    }
+    codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+    codeWriter.emitAnnotations(annotations, inlineAnnotations)
+    codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
+    codeWriter.emitCode(if (mutable) "var·" else "val·")
+    if (typeVariables.isNotEmpty()) {
+      codeWriter.emitTypeVariables(typeVariables)
+      codeWriter.emit(" ")
+    }
+    if (receiverType != null) {
+      if (receiverType is LambdaTypeName) {
+        codeWriter.emitCode("(%T).", receiverType)
+      } else {
+        codeWriter.emitCode("%T.", receiverType)
+      }
+    }
+    codeWriter.emitCode("%N: %T", this, type)
+    if (withInitializer && initializer != null) {
+      if (delegated) {
+        codeWriter.emit(" by ")
+      } else {
+        codeWriter.emitCode(" = ")
+      }
+      val initializerFormat = if (initializer.hasStatements()) "%L" else "«%L»"
+      codeWriter.emitCode(
+        codeBlock = CodeBlock.of(initializerFormat, initializer),
+        isConstantContext = KModifier.CONST in modifiers,
+      )
+    }
+    codeWriter.emitWhereBlock(typeVariables)
+    if (!inline) codeWriter.emit("\n")
+    val implicitAccessorModifiers = EnumSet.noneOf(KModifier::class.java)
+    for (modifier in implicitModifiers) {
+      // Omit visibility modifiers, accessor visibility will default to the property's visibility.
+      if (modifier !in VISIBILITY_MODIFIERS) {
+        implicitAccessorModifiers.add(modifier)
+      }
+    }
+    if (isInlineProperty) {
+      implicitAccessorModifiers.add(KModifier.INLINE)
+    }
+    if (getter != null) {
+      codeWriter.emitCode("⇥")
+      getter.emit(codeWriter, null, implicitAccessorModifiers, false)
+      codeWriter.emitCode("⇤")
+    }
+    if (setter != null) {
+      codeWriter.emitCode("⇥")
+      setter.emit(codeWriter, null, implicitAccessorModifiers, false)
+      codeWriter.emitCode("⇤")
+    }
+  }
+
+  internal fun fromPrimaryConstructorParameter(parameter: ParameterSpec): PropertySpec {
+    val builder = toBuilder()
+      .addAnnotations(parameter.annotations)
+    builder.isPrimaryConstructorParameter = true
+    builder.modifiers += parameter.modifiers
+    if (builder.kdoc.isEmpty()) {
+      builder.addKdoc(parameter.kdoc)
+    }
+    return builder.build()
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString { emit(this, emptySet()) }
+
+  @JvmOverloads
+  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+    val builder = Builder(name, type)
+    builder.mutable = mutable
+    builder.kdoc.add(kdoc)
+    builder.annotations += annotations
+    builder.modifiers += modifiers
+    builder.typeVariables += typeVariables
+    builder.initializer = initializer
+    builder.delegated = delegated
+    builder.setter = setter
+    builder.getter = getter
+    builder.receiverType = receiverType
+    builder.tags += tagMap.tags
+    builder.originatingElements += originatingElements
+    builder.contextReceiverTypes += contextReceiverTypes
+    return builder
+  }
+
+  public class Builder internal constructor(
+    internal val name: String,
+    internal val type: TypeName,
+  ) : Taggable.Builder<Builder>,
+    OriginatingElementsHolder.Builder<Builder>,
+    ContextReceivable.Builder<Builder> {
+    internal var isPrimaryConstructorParameter = false
+    internal var mutable = false
+    internal val kdoc = CodeBlock.builder()
+    internal var initializer: CodeBlock? = null
+    internal var delegated = false
+    internal var getter: FunSpec? = null
+    internal var setter: FunSpec? = null
+    internal var receiverType: TypeName? = null
+
+    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+    public val modifiers: MutableList<KModifier> = mutableListOf()
+    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+    override val originatingElements: MutableList<Element> = mutableListOf()
+    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+
+    /** True to create a `var` instead of a `val`. */
+    public fun mutable(mutable: Boolean = true): Builder = apply {
+      this.mutable = mutable
+    }
+
+    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+      kdoc.add(format, *args)
+    }
+
+    public fun addKdoc(block: CodeBlock): Builder = apply {
+      kdoc.add(block)
+    }
+
+    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+      annotations += annotationSpecs
+    }
+
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      annotations += annotationSpec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder = apply {
+      annotations += AnnotationSpec.builder(annotation).build()
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+      this.modifiers += modifiers
+    }
+
+    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+      this.typeVariables += typeVariables
+    }
+
+    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+      typeVariables += typeVariable
+    }
+
+    public fun initializer(format: String, vararg args: Any?): Builder =
+      initializer(CodeBlock.of(format, *args))
+
+    public fun initializer(codeBlock: CodeBlock?): Builder = apply {
+      this.initializer = codeBlock
+      this.delegated = false
+    }
+
+    public fun delegate(format: String, vararg args: Any?): Builder =
+      delegate(CodeBlock.of(format, *args))
+
+    public fun delegate(codeBlock: CodeBlock): Builder = apply {
+      this.initializer = codeBlock
+      this.delegated = true
+    }
+
+    public fun getter(getter: FunSpec?): Builder = apply {
+      require(getter == null || getter.name == GETTER) { "${getter!!.name} is not a getter" }
+      this.getter = getter
+    }
+
+    public fun setter(setter: FunSpec?): Builder = apply {
+      require(setter == null || setter.name == SETTER) { "${setter!!.name} is not a setter" }
+      this.setter = setter
+    }
+
+    public fun receiver(receiverType: TypeName?): Builder = apply {
+      this.receiverType = receiverType
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun receiver(receiverType: Type): Builder = receiver(receiverType.asTypeName())
+
+    public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName())
+
+    public fun build(): PropertySpec {
+      if (KModifier.INLINE in modifiers) {
+        throw IllegalArgumentException(
+          "KotlinPoet doesn't allow setting the inline modifier on " +
+            "properties. You should mark either the getter, the setter, or both inline.",
+        )
+      }
+      for (it in modifiers) {
+        if (!isPrimaryConstructorParameter) it.checkTarget(PROPERTY)
+      }
+      return PropertySpec(this)
+    }
+  }
+
+  public companion object {
+    @JvmStatic public fun builder(
+      name: String,
+      type: TypeName,
+      vararg modifiers: KModifier,
+    ): Builder {
+      return Builder(name, type).addModifiers(*modifiers)
+    }
+
+    @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
+      builder(name, type.asTypeName(), *modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: KClass<*>,
+      vararg modifiers: KModifier,
+    ): Builder = builder(name, type.asTypeName(), *modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: TypeName,
+      modifiers: Iterable<KModifier>,
+    ): Builder {
+      return Builder(name, type).addModifiers(modifiers)
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun builder(
+      name: String,
+      type: Type,
+      modifiers: Iterable<KModifier>,
+    ): Builder = builder(name, type.asTypeName(), modifiers)
+
+    @JvmStatic public fun builder(
+      name: String,
+      type: KClass<*>,
+      modifiers: Iterable<KModifier>,
+    ): Builder = builder(name, type.asTypeName(), modifiers)
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt
new file mode 100644
index 0000000..74ff690
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+/** A type that can be tagged with extra metadata of the user's choice. */
+public interface Taggable {
+
+  /** Returns all tags. */
+  public val tags: Map<KClass<*>, Any> get() = emptyMap()
+
+  /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
+  public fun <T : Any> tag(type: Class<T>): T? = tag(type.kotlin)
+
+  /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
+  public fun <T : Any> tag(type: KClass<T>): T? {
+    @Suppress("UNCHECKED_CAST")
+    return tags[type] as T?
+  }
+
+  /** The builder analogue to [Taggable] types. */
+  public interface Builder<out T : Builder<T>> {
+
+    /** Mutable map of the current tags this builder contains. */
+    public val tags: MutableMap<KClass<*>, Any>
+
+    /**
+     * Attaches [tag] to the request using [type] as a key. Tags can be read from a
+     * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+     * [type].
+     *
+     * Use this API to attach originating elements, debugging, or other application data to a spec
+     * so that you may read it in other APIs or callbacks.
+     */
+    public fun tag(type: Class<*>, tag: Any?): T = tag(type.kotlin, tag)
+
+    /**
+     * Attaches [tag] to the request using [type] as a key. Tags can be read from a
+     * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+     * [type].
+     *
+     * Use this API to attach originating elements, debugging, or other application data to a spec
+     * so that you may read it in other APIs or callbacks.
+     */
+    @Suppress("UNCHECKED_CAST")
+    public fun tag(type: KClass<*>, tag: Any?): T = apply {
+      if (tag == null) {
+        this.tags.remove(type)
+      } else {
+        this.tags[type] = tag
+      }
+    } as T
+  }
+}
+
+/** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */
+public inline fun <reified T : Any> Taggable.tag(): T? = tag(T::class)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+
+public inline fun <reified T : Any> AnnotationSpec.Builder.tag(tag: T?): AnnotationSpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> FileSpec.Builder.tag(tag: T?): FileSpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> FunSpec.Builder.tag(tag: T?): FunSpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> ParameterSpec.Builder.tag(tag: T?): ParameterSpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> PropertySpec.Builder.tag(tag: T?): PropertySpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> TypeAliasSpec.Builder.tag(tag: T?): TypeAliasSpec.Builder =
+  tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> TypeSpec.Builder.tag(tag: T?): TypeSpec.Builder =
+  tag(T::class, tag)
+
+internal fun Taggable.Builder<*>.buildTagMap(): TagMap = TagMap(tags)
+
+@JvmInline
+internal value class TagMap private constructor(override val tags: Map<KClass<*>, Any>) : Taggable {
+  companion object {
+    operator fun invoke(tags: Map<KClass<*>, Any>): TagMap = TagMap(tags.toImmutableMap())
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt
new file mode 100644
index 0000000..a0a90e2
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ACTUAL
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+
+/** A generated typealias declaration */
+public class TypeAliasSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+  public val name: String = builder.name
+  public val type: TypeName = builder.type
+  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+  public val kdoc: CodeBlock = builder.kdoc.build()
+  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+
+  internal fun emit(codeWriter: CodeWriter) {
+    codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+    codeWriter.emitAnnotations(annotations, false)
+    codeWriter.emitModifiers(modifiers, setOf(PUBLIC))
+    codeWriter.emitCode("typealias %N", name)
+    codeWriter.emitTypeVariables(typeVariables)
+    codeWriter.emitCode(" = %T", type)
+    codeWriter.emit("\n")
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString { emit(this) }
+
+  @JvmOverloads
+  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+    val builder = Builder(name, type)
+    builder.modifiers += modifiers
+    builder.typeVariables += typeVariables
+    builder.kdoc.add(kdoc)
+    builder.annotations += annotations
+    builder.tags += tagMap.tags
+    return builder
+  }
+
+  public class Builder internal constructor(
+    internal val name: String,
+    internal val type: TypeName,
+  ) : Taggable.Builder<Builder> {
+    internal val kdoc = CodeBlock.builder()
+
+    public val modifiers: MutableSet<KModifier> = mutableSetOf()
+    public val typeVariables: MutableSet<TypeVariableName> = mutableSetOf()
+    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+      modifiers.forEach(this::addModifier)
+    }
+
+    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+      modifiers.forEach(this::addModifier)
+    }
+
+    private fun addModifier(modifier: KModifier) {
+      this.modifiers.add(modifier)
+    }
+
+    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+      this.typeVariables += typeVariables
+    }
+
+    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+      typeVariables += typeVariable
+    }
+
+    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+      kdoc.add(format, *args)
+    }
+
+    public fun addKdoc(block: CodeBlock): Builder = apply {
+      kdoc.add(block)
+    }
+
+    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+      this.annotations += annotationSpecs
+    }
+
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      annotations += annotationSpec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder = apply {
+      annotations += AnnotationSpec.builder(annotation).build()
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun build(): TypeAliasSpec {
+      for (it in modifiers) {
+        require(it in ALLOWABLE_MODIFIERS) {
+          "unexpected typealias modifier $it"
+        }
+      }
+      return TypeAliasSpec(this)
+    }
+
+    private companion object {
+      private val ALLOWABLE_MODIFIERS = setOf(PUBLIC, INTERNAL, PRIVATE, ACTUAL)
+    }
+  }
+
+  public companion object {
+    @JvmStatic public fun builder(name: String, type: TypeName): Builder = Builder(name, type)
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun builder(name: String, type: Type): Builder =
+      builder(name, type.asTypeName())
+
+    @JvmStatic public fun builder(name: String, type: KClass<*>): Builder =
+      builder(name, type.asTypeName())
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt
new file mode 100644
index 0000000..bffcf4f
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+@file:JvmName("TypeNames")
+
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.lang.reflect.GenericArrayType
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.TypeVariable
+import java.lang.reflect.WildcardType
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.TypeParameterElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleTypeVisitor7
+import kotlin.reflect.KClass
+import kotlin.reflect.typeOf
+
+/**
+ * Any type in Kotlin's type system. This class identifies simple types like `Int` and `String`,
+ * nullable types like `Int?`, composite types like `Array<String>` and `Set<String>`, and
+ * unassignable types like `Unit`.
+ *
+ * Type names are dumb identifiers only and do not model the values they name. For example, the
+ * type name for `kotlin.List` doesn't know about the `size()` function, the fact that lists are
+ * collections, or even that it accepts a single type parameter.
+ *
+ * Instances of this class are immutable value objects that implement `equals()` and `hashCode()`
+ * properly.
+ *
+ * Referencing existing types
+ * --------------------------
+ *
+ * In an annotation processor you can get a type name instance for a type mirror by calling
+ * [asTypeName]. In reflection code, you can use [asTypeName].
+
+ * Defining new types
+ * ------------------
+ *
+ * Create new reference types like `com.example.HelloWorld` with [ClassName.bestGuess]. To build composite
+ * types like `Set<Long>`, use the factory methods on [ParameterizedTypeName], [TypeVariableName],
+ * and [WildcardTypeName].
+ */
+public sealed class TypeName constructor(
+  public val isNullable: Boolean,
+  annotations: List<AnnotationSpec>,
+  internal val tagMap: TagMap,
+) : Taggable by tagMap {
+  public val annotations: List<AnnotationSpec> = annotations.toImmutableList()
+
+  /** Lazily-initialized toString of this type name.  */
+  private val cachedString: String by lazy {
+    buildCodeString {
+      emitAnnotations(this)
+      emit(this)
+      if (isNullable) emit("?")
+    }
+  }
+
+  public fun copy(
+    nullable: Boolean = this.isNullable,
+    annotations: List<AnnotationSpec> = this.annotations.toList(),
+  ): TypeName {
+    return copy(nullable, annotations, this.tags)
+  }
+
+  public abstract fun copy(
+    nullable: Boolean = this.isNullable,
+    annotations: List<AnnotationSpec> = this.annotations.toList(),
+    tags: Map<KClass<*>, Any> = this.tags,
+  ): TypeName
+
+  public val isAnnotated: Boolean get() = annotations.isNotEmpty()
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = cachedString
+
+  internal abstract fun emit(out: CodeWriter): CodeWriter
+
+  internal fun emitAnnotations(out: CodeWriter) {
+    for (annotation in annotations) {
+      annotation.emit(out, true)
+      out.emit(" ")
+    }
+  }
+
+  internal fun emitNullable(out: CodeWriter) {
+    if (isNullable) {
+      out.emit("?")
+    }
+  }
+
+  public companion object {
+    internal fun get(
+      mirror: TypeMirror,
+      typeVariables: Map<TypeParameterElement, TypeVariableName>,
+    ): TypeName {
+      return mirror.accept(
+        object : SimpleTypeVisitor7<TypeName, Void?>() {
+          override fun visitPrimitive(t: PrimitiveType, p: Void?): TypeName {
+            return when (t.kind) {
+              TypeKind.BOOLEAN -> BOOLEAN
+              TypeKind.BYTE -> BYTE
+              TypeKind.SHORT -> SHORT
+              TypeKind.INT -> INT
+              TypeKind.LONG -> LONG
+              TypeKind.CHAR -> CHAR
+              TypeKind.FLOAT -> FLOAT
+              TypeKind.DOUBLE -> DOUBLE
+              else -> throw AssertionError()
+            }
+          }
+
+          override fun visitDeclared(t: DeclaredType, p: Void?): TypeName {
+            val rawType: ClassName = (t.asElement() as TypeElement).asClassName()
+            val enclosingType = t.enclosingType
+            val enclosing = if (enclosingType.kind != TypeKind.NONE &&
+              Modifier.STATIC !in t.asElement().modifiers
+            ) {
+              enclosingType.accept(this, null)
+            } else {
+              null
+            }
+            if (t.typeArguments.isEmpty() && enclosing !is ParameterizedTypeName) {
+              return rawType
+            }
+
+            val typeArgumentNames = mutableListOf<TypeName>()
+            for (typeArgument in t.typeArguments) {
+              typeArgumentNames += get(typeArgument, typeVariables)
+            }
+            return if (enclosing is ParameterizedTypeName) {
+              enclosing.nestedClass(rawType.simpleName, typeArgumentNames)
+            } else {
+              ParameterizedTypeName(null, rawType, typeArgumentNames)
+            }
+          }
+
+          override fun visitError(t: ErrorType, p: Void?): TypeName {
+            return visitDeclared(t, p)
+          }
+
+          override fun visitArray(t: ArrayType, p: Void?): ParameterizedTypeName {
+            return ARRAY.parameterizedBy(get(t.componentType, typeVariables))
+          }
+
+          override fun visitTypeVariable(
+            t: javax.lang.model.type.TypeVariable,
+            p: Void?,
+          ): TypeName {
+            return TypeVariableName.get(t, typeVariables.toMutableMap())
+          }
+
+          override fun visitWildcard(t: javax.lang.model.type.WildcardType, p: Void?): TypeName {
+            return WildcardTypeName.get(t, typeVariables)
+          }
+
+          override fun visitNoType(t: NoType, p: Void?): TypeName {
+            if (t.kind == TypeKind.VOID) return UNIT
+            return super.visitUnknown(t, p)
+          }
+
+          override fun defaultAction(e: TypeMirror?, p: Void?): TypeName {
+            throw IllegalArgumentException("Unexpected type mirror: " + e!!)
+          }
+        },
+        null,
+      )
+    }
+
+    internal fun get(type: Type, map: MutableMap<Type, TypeVariableName>): TypeName {
+      return when (type) {
+        is Class<*> -> when {
+          type === Void.TYPE -> UNIT
+          type === Boolean::class.javaPrimitiveType -> BOOLEAN
+          type === Byte::class.javaPrimitiveType -> BYTE
+          type === Short::class.javaPrimitiveType -> SHORT
+          type === Int::class.javaPrimitiveType -> INT
+          type === Long::class.javaPrimitiveType -> LONG
+          type === Char::class.javaPrimitiveType -> CHAR
+          type === Float::class.javaPrimitiveType -> FLOAT
+          type === Double::class.javaPrimitiveType -> DOUBLE
+          type.isArray -> ARRAY.parameterizedBy(get(type.componentType, map))
+          else -> type.asClassName()
+        }
+        is ParameterizedType -> ParameterizedTypeName.get(type, map)
+        is WildcardType -> WildcardTypeName.get(type, map)
+        is TypeVariable<*> -> TypeVariableName.get(type, map)
+        is GenericArrayType -> ARRAY.parameterizedBy(get(type.genericComponentType, map))
+        else -> throw IllegalArgumentException("unexpected type: $type")
+      }
+    }
+  }
+}
+
+@JvmField public val ANY: ClassName = ClassName("kotlin", "Any")
+
+@JvmField public val ARRAY: ClassName = ClassName("kotlin", "Array")
+
+@JvmField public val UNIT: ClassName = ClassName("kotlin", "Unit")
+
+@JvmField public val BOOLEAN: ClassName = ClassName("kotlin", "Boolean")
+
+@JvmField public val BYTE: ClassName = ClassName("kotlin", "Byte")
+
+@JvmField public val SHORT: ClassName = ClassName("kotlin", "Short")
+
+@JvmField public val INT: ClassName = ClassName("kotlin", "Int")
+
+@JvmField public val LONG: ClassName = ClassName("kotlin", "Long")
+
+@JvmField public val CHAR: ClassName = ClassName("kotlin", "Char")
+
+@JvmField public val FLOAT: ClassName = ClassName("kotlin", "Float")
+
+@JvmField public val DOUBLE: ClassName = ClassName("kotlin", "Double")
+
+@JvmField public val STRING: ClassName = ClassName("kotlin", "String")
+
+@JvmField public val CHAR_SEQUENCE: ClassName = ClassName("kotlin", "CharSequence")
+
+@JvmField public val COMPARABLE: ClassName = ClassName("kotlin", "Comparable")
+
+@JvmField public val THROWABLE: ClassName = ClassName("kotlin", "Throwable")
+
+@JvmField public val ANNOTATION: ClassName = ClassName("kotlin", "Annotation")
+
+@JvmField public val NOTHING: ClassName = ClassName("kotlin", "Nothing")
+
+@JvmField public val NUMBER: ClassName = ClassName("kotlin", "Number")
+
+@JvmField public val ITERABLE: ClassName = ClassName("kotlin.collections", "Iterable")
+
+@JvmField public val COLLECTION: ClassName = ClassName("kotlin.collections", "Collection")
+
+@JvmField public val LIST: ClassName = ClassName("kotlin.collections", "List")
+
+@JvmField public val SET: ClassName = ClassName("kotlin.collections", "Set")
+
+@JvmField public val MAP: ClassName = ClassName("kotlin.collections", "Map")
+
+@JvmField public val MAP_ENTRY: ClassName = MAP.nestedClass("Entry")
+
+@JvmField public val MUTABLE_ITERABLE: ClassName =
+  ClassName("kotlin.collections", "MutableIterable")
+
+@JvmField public val MUTABLE_COLLECTION: ClassName =
+  ClassName("kotlin.collections", "MutableCollection")
+
+@JvmField public val MUTABLE_LIST: ClassName = ClassName("kotlin.collections", "MutableList")
+
+@JvmField public val MUTABLE_SET: ClassName = ClassName("kotlin.collections", "MutableSet")
+
+@JvmField public val MUTABLE_MAP: ClassName = ClassName("kotlin.collections", "MutableMap")
+
+@JvmField public val MUTABLE_MAP_ENTRY: ClassName = MUTABLE_MAP.nestedClass("Entry")
+
+@JvmField public val BOOLEAN_ARRAY: ClassName = ClassName("kotlin", "BooleanArray")
+
+@JvmField public val BYTE_ARRAY: ClassName = ClassName("kotlin", "ByteArray")
+
+@JvmField public val CHAR_ARRAY: ClassName = ClassName("kotlin", "CharArray")
+
+@JvmField public val SHORT_ARRAY: ClassName = ClassName("kotlin", "ShortArray")
+
+@JvmField public val INT_ARRAY: ClassName = ClassName("kotlin", "IntArray")
+
+@JvmField public val LONG_ARRAY: ClassName = ClassName("kotlin", "LongArray")
+
+@JvmField public val FLOAT_ARRAY: ClassName = ClassName("kotlin", "FloatArray")
+
+@JvmField public val DOUBLE_ARRAY: ClassName = ClassName("kotlin", "DoubleArray")
+
+@JvmField public val ENUM: ClassName = ClassName("kotlin", "Enum")
+
+@JvmField public val U_BYTE: ClassName = ClassName("kotlin", "UByte")
+
+@JvmField public val U_SHORT: ClassName = ClassName("kotlin", "UShort")
+
+@JvmField public val U_INT: ClassName = ClassName("kotlin", "UInt")
+
+@JvmField public val U_LONG: ClassName = ClassName("kotlin", "ULong")
+
+@JvmField public val U_BYTE_ARRAY: ClassName = ClassName("kotlin", "UByteArray")
+
+@JvmField public val U_SHORT_ARRAY: ClassName = ClassName("kotlin", "UShortArray")
+
+@JvmField public val U_INT_ARRAY: ClassName = ClassName("kotlin", "UIntArray")
+
+@JvmField public val U_LONG_ARRAY: ClassName = ClassName("kotlin", "ULongArray")
+
+/** The wildcard type `*` which is shorthand for `out Any?`. */
+@JvmField public val STAR: WildcardTypeName = WildcardTypeName.producerOf(ANY.copy(nullable = true))
+
+/** [Dynamic] is a singleton `object` type, so this is a shorthand for it in Java. */
+@JvmField public val DYNAMIC: Dynamic = Dynamic
+
+/** Returns a [TypeName] equivalent to this [TypeMirror]. */
+@DelicateKotlinPoetApi(
+  message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeMirror.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())
+
+/** Returns a [TypeName] equivalent to this [KClass].  */
+@JvmName("get")
+public fun KClass<*>.asTypeName(): ClassName = asClassName()
+
+/** Returns a [TypeName] equivalent to this [Type].  */
+@JvmName("get")
+public fun Type.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())
+
+/**
+ * Returns a [TypeName] equivalent of the reified type parameter [T] using reflection, maybe using kotlin-reflect
+ * if required.
+ */
+public inline fun <reified T> typeNameOf(): TypeName = typeOf<T>().asTypeName()
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
new file mode 100644
index 0000000..c8abca9
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.ANNOTATION
+import com.squareup.kotlinpoet.KModifier.COMPANION
+import com.squareup.kotlinpoet.KModifier.ENUM
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.FUN
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.SEALED
+import com.squareup.kotlinpoet.KModifier.VALUE
+import java.lang.reflect.Type
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/** A generated class, interface, or enum declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class TypeSpec private constructor(
+  builder: Builder,
+  private val tagMap: TagMap = builder.buildTagMap(),
+  private val delegateOriginatingElements: OriginatingElementsHolder = builder.originatingElements
+    .plus(builder.typeSpecs.flatMap(TypeSpec::originatingElements))
+    .buildOriginatingElements(),
+  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElements, ContextReceivable by contextReceivers {
+  public val kind: Kind = builder.kind
+  public val name: String? = builder.name
+  public val kdoc: CodeBlock = builder.kdoc.build()
+  public val annotationSpecs: List<AnnotationSpec> = builder.annotationSpecs.toImmutableList()
+  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+  public val primaryConstructor: FunSpec? = builder.primaryConstructor
+  public val superclass: TypeName = builder.superclass
+  public val superclassConstructorParameters: List<CodeBlock> =
+    builder.superclassConstructorParameters.toImmutableList()
+
+  public val isEnum: Boolean = builder.isEnum
+  public val isAnnotation: Boolean = builder.isAnnotation
+  public val isCompanion: Boolean = builder.isCompanion
+  public val isAnonymousClass: Boolean = builder.isAnonymousClass
+  public val isFunctionalInterface: Boolean = builder.isFunInterface
+
+  /**
+   * Map of superinterfaces - entries with a null value represent a regular superinterface (with
+   * no delegation), while non-null [CodeBlock] values represent delegates
+   * for the corresponding [TypeSpec] interface (key) value
+   */
+  public val superinterfaces: Map<TypeName, CodeBlock?> = builder.superinterfaces.toImmutableMap()
+  public val enumConstants: Map<String, TypeSpec> = builder.enumConstants.toImmutableMap()
+  public val propertySpecs: List<PropertySpec> = builder.propertySpecs.toImmutableList()
+  public val initializerBlock: CodeBlock = builder.initializerBlock.build()
+  public val initializerIndex: Int = builder.initializerIndex
+  public val funSpecs: List<FunSpec> = builder.funSpecs.toImmutableList()
+  public val typeSpecs: List<TypeSpec> = builder.typeSpecs.toImmutableList()
+  internal val nestedTypesSimpleNames = typeSpecs.map { it.name }.toImmutableSet()
+
+  @JvmOverloads
+  public fun toBuilder(kind: Kind = this.kind, name: String? = this.name): Builder {
+    val builder = Builder(kind, name)
+    builder.modifiers += modifiers
+    builder.kdoc.add(kdoc)
+    builder.annotationSpecs += annotationSpecs
+    builder.typeVariables += typeVariables
+    builder.superclass = superclass
+    builder.superclassConstructorParameters += superclassConstructorParameters
+    builder.enumConstants += enumConstants
+    builder.propertySpecs += propertySpecs
+    builder.funSpecs += funSpecs
+    builder.typeSpecs += typeSpecs
+    builder.initializerBlock.add(initializerBlock)
+    builder.initializerIndex = initializerIndex
+    builder.superinterfaces.putAll(superinterfaces)
+    builder.primaryConstructor = primaryConstructor
+    builder.tags += tagMap.tags
+    builder.originatingElements += originatingElements
+    builder.contextReceiverTypes += contextReceiverTypes
+    return builder
+  }
+
+  internal fun emit(
+    codeWriter: CodeWriter,
+    enumName: String?,
+    implicitModifiers: Set<KModifier> = emptySet(),
+    isNestedExternal: Boolean = false,
+  ) {
+    // Types.
+    val areNestedExternal = EXTERNAL in modifiers || isNestedExternal
+
+    // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
+    // it back afterwards when this type is complete.
+    val previousStatementLine = codeWriter.statementLine
+    codeWriter.statementLine = -1
+
+    val constructorProperties: Map<String, PropertySpec> = constructorProperties()
+    val superclassConstructorParametersBlock = superclassConstructorParameters.joinToCode()
+
+    try {
+      if (enumName != null) {
+        codeWriter.emitKdoc(kdocWithConstructorParameters())
+        codeWriter.emitAnnotations(annotationSpecs, false)
+        codeWriter.emitCode("%N", enumName)
+        if (superclassConstructorParametersBlock.isNotEmpty()) {
+          codeWriter.emit("(")
+          codeWriter.emitCode(superclassConstructorParametersBlock)
+          codeWriter.emit(")")
+        }
+        if (hasNoBody) {
+          return // Avoid unnecessary braces "{}".
+        }
+        codeWriter.emit(" {\n")
+      } else if (isAnonymousClass) {
+        codeWriter.emitCode("object")
+        val supertype = if (superclass != ANY) {
+          if (!areNestedExternal && !modifiers.contains(EXPECT)) {
+            listOf(CodeBlock.of(" %T(%L)", superclass, superclassConstructorParametersBlock))
+          } else {
+            listOf(CodeBlock.of(" %T", superclass))
+          }
+        } else {
+          listOf()
+        }
+
+        val allSuperTypes = supertype + if (superinterfaces.isNotEmpty()) {
+          superinterfaces.keys.map { CodeBlock.of(" %T", it) }
+        } else {
+          emptyList()
+        }
+
+        if (allSuperTypes.isNotEmpty()) {
+          codeWriter.emitCode(" :")
+          codeWriter.emitCode(allSuperTypes.joinToCode(","))
+        }
+        if (hasNoBody) {
+          codeWriter.emit(" {\n}")
+          return
+        }
+        codeWriter.emit(" {\n")
+      } else {
+        codeWriter.emitKdoc(kdocWithConstructorParameters())
+        codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+        codeWriter.emitAnnotations(annotationSpecs, false)
+        codeWriter.emitModifiers(
+          modifiers,
+          if (isNestedExternal) setOf(PUBLIC, EXTERNAL) else setOf(PUBLIC),
+        )
+        codeWriter.emit(kind.declarationKeyword)
+        if (name != null) {
+          codeWriter.emitCode(" %N", this)
+        }
+        codeWriter.emitTypeVariables(typeVariables)
+
+        primaryConstructor?.let {
+          codeWriter.pushType(this) // avoid name collisions when emitting primary constructor
+          val emittedAnnotations = it.annotations.isNotEmpty()
+          val useKeyword = it.annotations.isNotEmpty() || it.modifiers.isNotEmpty()
+
+          if (it.annotations.isNotEmpty()) {
+            codeWriter.emit(" ")
+            codeWriter.emitAnnotations(it.annotations, true)
+          }
+
+          if (it.modifiers.isNotEmpty()) {
+            if (!emittedAnnotations) codeWriter.emit(" ")
+            codeWriter.emitModifiers(it.modifiers)
+          }
+
+          if (useKeyword) {
+            codeWriter.emit("constructor")
+          }
+
+          it.parameters.emit(codeWriter, forceNewLines = true) { param ->
+            val property = constructorProperties[param.name]
+            if (property != null) {
+              property.emit(
+                codeWriter,
+                setOf(PUBLIC),
+                withInitializer = false,
+                inline = true,
+                inlineAnnotations = false,
+              )
+              param.emitDefaultValue(codeWriter)
+            } else {
+              param.emit(codeWriter, emitKdoc = true, inlineAnnotations = false)
+            }
+          }
+
+          codeWriter.popType()
+        }
+
+        val types = listOf(superclass).filter { it != ANY }.map {
+          if (primaryConstructor != null || funSpecs.none(FunSpec::isConstructor)) {
+            if (!areNestedExternal && !modifiers.contains(EXPECT)) {
+              CodeBlock.of("%T(%L)", it, superclassConstructorParametersBlock)
+            } else {
+              CodeBlock.of("%T", it)
+            }
+          } else {
+            CodeBlock.of("%T", it)
+          }
+        }
+        val superTypes = types + superinterfaces.entries.map { (type, init) ->
+          if (init == null) CodeBlock.of("%T", type) else CodeBlock.of("%T by %L", type, init)
+        }
+
+        if (superTypes.isNotEmpty()) {
+          codeWriter.emitCode(superTypes.joinToCode(separator = ", ", prefix = " : "))
+        }
+
+        codeWriter.emitWhereBlock(typeVariables)
+
+        if (hasNoBody) {
+          codeWriter.emit("\n")
+          return // Avoid unnecessary braces "{}".
+        }
+        codeWriter.emit(" {\n")
+      }
+
+      codeWriter.pushType(this)
+      codeWriter.indent()
+      var firstMember = true
+      for ((key, value) in enumConstants.entries) {
+        if (!firstMember) codeWriter.emit("\n")
+        value.emit(codeWriter, key)
+        codeWriter.emit(",")
+        firstMember = false
+      }
+      if (isEnum) {
+        if (!firstMember) {
+          codeWriter.emit("\n")
+        }
+        if (propertySpecs.isNotEmpty() || funSpecs.isNotEmpty() || typeSpecs.isNotEmpty()) {
+          codeWriter.emit(";\n")
+        }
+      }
+
+      val cachedHasInitializer = hasInitializer
+      var initializerEmitted = false
+      fun possiblyEmitInitializer() {
+        if (initializerEmitted) return
+        initializerEmitted = true
+        if (cachedHasInitializer) {
+          if (!firstMember) codeWriter.emit("\n")
+          codeWriter.emitCode(initializerBlock)
+          firstMember = false
+        }
+      }
+
+      // Properties and initializer block.
+      for ((index, propertySpec) in propertySpecs.withIndex()) {
+        // Initializer block.
+        if (index == initializerIndex) {
+          possiblyEmitInitializer()
+        }
+        if (constructorProperties.containsKey(propertySpec.name)) {
+          continue
+        }
+        if (!firstMember) codeWriter.emit("\n")
+        propertySpec.emit(codeWriter, kind.implicitPropertyModifiers(modifiers))
+        firstMember = false
+      }
+
+      // One last try in case the initializer index is after all properties
+      possiblyEmitInitializer()
+
+      if (primaryConstructor != null && primaryConstructor.body.isNotEmpty()) {
+        codeWriter.emit("init {\n")
+        codeWriter.indent()
+        codeWriter.emitCode(primaryConstructor.body)
+        codeWriter.unindent()
+        codeWriter.emit("}\n")
+      }
+
+      // Constructors.
+      for (funSpec in funSpecs) {
+        if (!funSpec.isConstructor) continue
+        if (!firstMember) codeWriter.emit("\n")
+        funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), false)
+        firstMember = false
+      }
+
+      // Functions.
+      for (funSpec in funSpecs) {
+        if (funSpec.isConstructor) continue
+        if (!firstMember) codeWriter.emit("\n")
+        funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), true)
+        firstMember = false
+      }
+
+      for (typeSpec in typeSpecs) {
+        if (!firstMember) codeWriter.emit("\n")
+        typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers(modifiers + implicitModifiers), isNestedExternal = areNestedExternal)
+        firstMember = false
+      }
+
+      codeWriter.unindent()
+      codeWriter.popType()
+
+      codeWriter.emit("}")
+      if (enumName == null && !isAnonymousClass) {
+        codeWriter.emit("\n") // If this type isn't also a value, include a trailing newline.
+      }
+    } finally {
+      codeWriter.statementLine = previousStatementLine
+    }
+  }
+
+  /** Returns the properties that can be declared inline as constructor parameters. */
+  private fun constructorProperties(): Map<String, PropertySpec> {
+    if (primaryConstructor == null) return emptyMap()
+
+    // Properties added after the initializer are not permitted to be inlined into the constructor
+    // due to ordering concerns.
+    val range = if (hasInitializer) {
+      0 until initializerIndex
+    } else {
+      propertySpecs.indices
+    }
+    val result: MutableMap<String, PropertySpec> = LinkedHashMap()
+    for (propertyIndex in range) {
+      val property = propertySpecs[propertyIndex]
+      if (property.getter != null || property.setter != null) continue
+      val parameter = primaryConstructor.parameter(property.name) ?: continue
+      if (parameter.type != property.type) continue
+      if (!isPropertyInitializerConstructorParameter(property, parameter)) {
+        continue
+      }
+
+      result[property.name] = property.fromPrimaryConstructorParameter(parameter)
+    }
+    return result
+  }
+
+  /**
+   * Returns true if the property can be declared inline as a constructor parameter
+   */
+  private fun isPropertyInitializerConstructorParameter(
+    property: PropertySpec,
+    parameter: ParameterSpec,
+  ): Boolean {
+    val parameterName = CodeBlock.of("%N", parameter).toString()
+    val initializerCode = property.initializer.toString().escapeIfNecessary(validate = false)
+    return parameterName == initializerCode
+  }
+
+  /**
+   * Returns KDoc comments including those of primary constructor parameters.
+   *
+   * If the primary constructor parameter is not mapped to a property, or if the property doesn't
+   * have its own KDoc - the parameter's KDoc will be attached to the parameter. Otherwise, if both
+   * the parameter and the property have KDoc - the property's KDoc will be attached to the
+   * property/parameter, and the parameter's KDoc will be printed in the type header.
+   */
+  private fun kdocWithConstructorParameters(): CodeBlock {
+    if (primaryConstructor == null || primaryConstructor.parameters.isEmpty()) {
+      return kdoc.ensureEndsWithNewLine()
+    }
+    val constructorProperties = constructorProperties()
+    val parametersWithKdoc = primaryConstructor.parameters.filter { parameter ->
+      val propertyKdoc = constructorProperties[parameter.name]?.kdoc ?: CodeBlock.EMPTY
+      return@filter parameter.kdoc.isNotEmpty() && propertyKdoc.isNotEmpty() &&
+        parameter.kdoc != propertyKdoc
+    }
+    return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
+      parametersWithKdoc.forEachIndexed { index, parameter ->
+        if (index == 0 && kdoc.isNotEmpty()) add("\n")
+        add("@param %L %L", parameter.name, parameter.kdoc.ensureEndsWithNewLine())
+      }
+      build()
+    }
+  }
+
+  private val hasInitializer: Boolean get() = initializerIndex != -1 && initializerBlock.isNotEmpty()
+
+  private val hasNoBody: Boolean
+    get() {
+      if (propertySpecs.isNotEmpty()) {
+        val constructorProperties = constructorProperties()
+        for (propertySpec in propertySpecs) {
+          if (!constructorProperties.containsKey(propertySpec.name)) {
+            return false
+          }
+        }
+      }
+      return enumConstants.isEmpty() &&
+        initializerBlock.isEmpty() &&
+        (primaryConstructor?.body?.isEmpty() ?: true) &&
+        funSpecs.isEmpty() &&
+        typeSpecs.isEmpty()
+    }
+
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null) return false
+    if (javaClass != other.javaClass) return false
+    return toString() == other.toString()
+  }
+
+  override fun hashCode(): Int = toString().hashCode()
+
+  override fun toString(): String = buildCodeString { emit(this, null) }
+
+  public enum class Kind(
+    internal val declarationKeyword: String,
+    internal val defaultImplicitPropertyModifiers: Set<KModifier>,
+    internal val defaultImplicitFunctionModifiers: Set<KModifier>,
+    internal val defaultImplicitTypeModifiers: Set<KModifier>,
+  ) {
+    CLASS("class", setOf(PUBLIC), setOf(PUBLIC), setOf()),
+    OBJECT("object", setOf(PUBLIC), setOf(PUBLIC), setOf()),
+    INTERFACE("interface", setOf(PUBLIC, ABSTRACT), setOf(PUBLIC, ABSTRACT), setOf()),
+    ;
+
+    internal fun implicitPropertyModifiers(modifiers: Set<KModifier>): Set<KModifier> {
+      return defaultImplicitPropertyModifiers + when {
+        ANNOTATION in modifiers -> emptySet()
+        EXPECT in modifiers -> setOf(EXPECT)
+        EXTERNAL in modifiers -> setOf(EXTERNAL)
+        else -> emptySet()
+      }
+    }
+
+    internal fun implicitFunctionModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
+      return defaultImplicitFunctionModifiers + when {
+        ANNOTATION in modifiers -> setOf(ABSTRACT)
+        EXPECT in modifiers -> setOf(EXPECT)
+        EXTERNAL in modifiers -> setOf(EXTERNAL)
+        else -> emptySet()
+      }
+    }
+
+    internal fun implicitTypeModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
+      return defaultImplicitTypeModifiers + when {
+        EXPECT in modifiers -> setOf(EXPECT)
+        EXTERNAL in modifiers -> setOf(EXTERNAL)
+        else -> emptySet()
+      }
+    }
+  }
+
+  public class Builder internal constructor(
+    internal var kind: Kind,
+    internal val name: String?,
+    vararg modifiers: KModifier,
+  ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
+    internal val kdoc = CodeBlock.builder()
+    internal var primaryConstructor: FunSpec? = null
+    internal var superclass: TypeName = ANY
+    internal val initializerBlock = CodeBlock.builder()
+    public var initializerIndex: Int = -1
+    internal val isAnonymousClass get() = name == null && kind == Kind.CLASS
+    internal val isExternal get() = EXTERNAL in modifiers
+    internal val isEnum get() = kind == Kind.CLASS && ENUM in modifiers
+    internal val isAnnotation get() = kind == Kind.CLASS && ANNOTATION in modifiers
+    internal val isCompanion get() = kind == Kind.OBJECT && COMPANION in modifiers
+    internal val isInlineOrValClass get() = kind == Kind.CLASS &&
+      (INLINE in modifiers || VALUE in modifiers)
+    internal val isSimpleClass get() = kind == Kind.CLASS && !isEnum && !isAnnotation
+    internal val isFunInterface get() = kind == Kind.INTERFACE && FUN in modifiers
+
+    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+    override val originatingElements: MutableList<Element> = mutableListOf()
+
+    @ExperimentalKotlinPoetApi
+    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+    public val modifiers: MutableSet<KModifier> = mutableSetOf(*modifiers)
+    public val superinterfaces: MutableMap<TypeName, CodeBlock?> = mutableMapOf()
+    public val enumConstants: MutableMap<String, TypeSpec> = mutableMapOf()
+    public val annotationSpecs: MutableList<AnnotationSpec> = mutableListOf()
+    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+    public val superclassConstructorParameters: MutableList<CodeBlock> = mutableListOf()
+    public val propertySpecs: MutableList<PropertySpec> = mutableListOf()
+    public val funSpecs: MutableList<FunSpec> = mutableListOf()
+    public val typeSpecs: MutableList<TypeSpec> = mutableListOf()
+
+    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+      kdoc.add(format, *args)
+    }
+
+    public fun addKdoc(block: CodeBlock): Builder = apply {
+      kdoc.add(block)
+    }
+
+    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+      this.annotationSpecs += annotationSpecs
+    }
+
+    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+      annotationSpecs += annotationSpec
+    }
+
+    public fun addAnnotation(annotation: ClassName): Builder =
+      addAnnotation(AnnotationSpec.builder(annotation).build())
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addAnnotation(annotation: Class<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addAnnotation(annotation: KClass<*>): Builder =
+      addAnnotation(annotation.asClassName())
+
+    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+      check(!isAnonymousClass) { "forbidden on anonymous types." }
+      this.modifiers += modifiers
+    }
+
+    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+      check(!isAnonymousClass) { "forbidden on anonymous types." }
+      this.modifiers += modifiers
+    }
+
+    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+      this.typeVariables += typeVariables
+    }
+
+    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+      typeVariables += typeVariable
+    }
+
+    public fun primaryConstructor(primaryConstructor: FunSpec?): Builder = apply {
+      check(kind == Kind.CLASS) {
+        "$kind can't have a primary constructor"
+      }
+      if (primaryConstructor != null) {
+        require(primaryConstructor.isConstructor) {
+          "expected a constructor but was ${primaryConstructor.name}"
+        }
+
+        if (isInlineOrValClass) {
+          check(primaryConstructor.parameters.size == 1) {
+            "value/inline classes must have 1 parameter in constructor"
+          }
+        }
+      }
+      this.primaryConstructor = primaryConstructor
+    }
+
+    public fun superclass(superclass: TypeName): Builder = apply {
+      checkCanHaveSuperclass()
+      check(this.superclass === ANY) { "superclass already set to ${this.superclass}" }
+      this.superclass = superclass
+    }
+
+    private fun checkCanHaveSuperclass() {
+      check(isSimpleClass || kind == Kind.OBJECT) {
+        "only classes can have super classes, not $kind"
+      }
+      check(!isInlineOrValClass) {
+        "value/inline classes cannot have super classes"
+      }
+    }
+
+    private fun checkCanHaveInitializerBlocks() {
+      check(isSimpleClass || isEnum || kind == Kind.OBJECT) {
+        "$kind can't have initializer blocks"
+      }
+      check(EXPECT !in modifiers) {
+        "expect $kind can't have initializer blocks"
+      }
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun superclass(superclass: Type): Builder = superclass(superclass.asTypeName())
+
+    public fun superclass(superclass: KClass<*>): Builder = superclass(superclass.asTypeName())
+
+    public fun addSuperclassConstructorParameter(
+      format: String,
+      vararg args: Any,
+    ): Builder = apply {
+      addSuperclassConstructorParameter(CodeBlock.of(format, *args))
+    }
+
+    public fun addSuperclassConstructorParameter(codeBlock: CodeBlock): Builder = apply {
+      checkCanHaveSuperclass()
+      this.superclassConstructorParameters += codeBlock
+    }
+
+    public fun addSuperinterfaces(superinterfaces: Iterable<TypeName>): Builder = apply {
+      this.superinterfaces.putAll(superinterfaces.map { it to null })
+    }
+
+    public fun addSuperinterface(
+      superinterface: TypeName,
+      delegate: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = apply {
+      if (delegate.isEmpty()) {
+        this.superinterfaces[superinterface] = null
+      } else {
+        require(isSimpleClass || kind == Kind.OBJECT) {
+          "delegation only allowed for classes and objects (found $kind '$name')"
+        }
+        require(!superinterface.isNullable) {
+          "expected non-nullable type but was '${superinterface.copy(nullable = false)}'"
+        }
+        require(this.superinterfaces[superinterface] == null) {
+          "'$name' can not delegate to $superinterface by $delegate with existing declaration by " +
+            "${this.superinterfaces[superinterface]}"
+        }
+        this.superinterfaces[superinterface] = delegate
+      }
+    }
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addSuperinterface(
+      superinterface: Type,
+      delegate: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)
+
+    public fun addSuperinterface(
+      superinterface: KClass<*>,
+      delegate: CodeBlock = CodeBlock.EMPTY,
+    ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)
+
+    public fun addSuperinterface(
+      superinterface: KClass<*>,
+      constructorParameterName: String,
+    ): Builder = addSuperinterface(superinterface.asTypeName(), constructorParameterName)
+
+    public fun addSuperinterface(
+      superinterface: TypeName,
+      constructorParameter: String,
+    ): Builder = apply {
+      requireNotNull(primaryConstructor) {
+        "delegating to constructor parameter requires not-null constructor"
+      }
+      val parameter = primaryConstructor?.parameter(constructorParameter)
+      requireNotNull(parameter) {
+        "no such constructor parameter '$constructorParameter' to delegate to for type '$name'"
+      }
+      addSuperinterface(superinterface, CodeBlock.of(constructorParameter))
+    }
+
+    @JvmOverloads public fun addEnumConstant(
+      name: String,
+      typeSpec: TypeSpec = anonymousClassBuilder().build(),
+    ): Builder = apply {
+      require(name != "name" && name != "ordinal") {
+        "constant with name \"$name\" conflicts with a supertype member with the same name"
+      }
+      enumConstants[name] = typeSpec
+    }
+
+    public fun addProperties(propertySpecs: Iterable<PropertySpec>): Builder = apply {
+      propertySpecs.map(this::addProperty)
+    }
+
+    public fun addProperty(propertySpec: PropertySpec): Builder = apply {
+      if (EXPECT in modifiers) {
+        require(propertySpec.initializer == null) {
+          "properties in expect classes can't have initializers"
+        }
+        require(propertySpec.getter == null && propertySpec.setter == null) {
+          "properties in expect classes can't have getters and setters"
+        }
+      }
+      if (isEnum) {
+        require(propertySpec.name != "name" && propertySpec.name != "ordinal") {
+          "${propertySpec.name} is a final supertype member and can't be redeclared or overridden"
+        }
+      }
+      propertySpecs += propertySpec
+    }
+
+    public fun addProperty(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
+      addProperty(PropertySpec.builder(name, type, *modifiers).build())
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addProperty(name: String, type: Type, vararg modifiers: KModifier): Builder =
+      addProperty(name, type.asTypeName(), *modifiers)
+
+    public fun addProperty(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
+      addProperty(name, type.asTypeName(), *modifiers)
+
+    public fun addProperty(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
+      addProperty(PropertySpec.builder(name, type, modifiers).build())
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    public fun addProperty(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
+      addProperty(name, type.asTypeName(), modifiers)
+
+    public fun addProperty(name: String, type: KClass<*>, modifiers: Iterable<KModifier>): Builder =
+      addProperty(name, type.asTypeName(), modifiers)
+
+    public fun addInitializerBlock(block: CodeBlock): Builder = apply {
+      checkCanHaveInitializerBlocks()
+      // Set index to however many properties we have
+      // All properties added after this point are declared as such, including any that initialize
+      // to a constructor param.
+      initializerIndex = propertySpecs.size
+      initializerBlock.add("init {\n")
+        .indent()
+        .add(block)
+        .unindent()
+        .add("}\n")
+    }
+
+    public fun addFunctions(funSpecs: Iterable<FunSpec>): Builder = apply {
+      funSpecs.forEach { addFunction(it) }
+    }
+
+    public fun addFunction(funSpec: FunSpec): Builder = apply {
+      funSpecs += funSpec
+    }
+
+    public fun addTypes(typeSpecs: Iterable<TypeSpec>): Builder = apply {
+      this.typeSpecs += typeSpecs
+    }
+
+    public fun addType(typeSpec: TypeSpec): Builder = apply {
+      typeSpecs += typeSpec
+    }
+
+    @ExperimentalKotlinPoetApi
+    override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
+      check(isSimpleClass) { "contextReceivers can only be applied on simple classes" }
+      contextReceiverTypes += receiverTypes
+    }
+
+    public fun build(): TypeSpec {
+      if (enumConstants.isNotEmpty()) {
+        check(isEnum) { "$name is not an enum and cannot have enum constants" }
+      }
+
+      if (superclassConstructorParameters.isNotEmpty()) {
+        checkCanHaveSuperclass()
+
+        check(!isExternal) {
+          "delegated constructor call in external class is not allowed"
+        }
+      }
+      check(!(isExternal && funSpecs.any { it.delegateConstructor != null })) {
+        "delegated constructor call in external class is not allowed"
+      }
+
+      check(!(isAnonymousClass && typeVariables.isNotEmpty())) {
+        "typevariables are forbidden on anonymous types"
+      }
+
+      val isAbstract = ABSTRACT in modifiers || SEALED in modifiers || kind != Kind.CLASS ||
+        !isSimpleClass
+      for (funSpec in funSpecs) {
+        require(isAbstract || ABSTRACT !in funSpec.modifiers) {
+          "non-abstract type $name cannot declare abstract function ${funSpec.name}"
+        }
+        when {
+          kind == Kind.INTERFACE -> {
+            requireNoneOf(funSpec.modifiers, INTERNAL, PROTECTED)
+            requireNoneOrOneOf(funSpec.modifiers, ABSTRACT, PRIVATE)
+          }
+          isAnnotation -> require(funSpec.modifiers == kind.implicitFunctionModifiers(modifiers)) {
+            "annotation class $name.${funSpec.name} " +
+              "requires modifiers ${kind.implicitFunctionModifiers(modifiers)}"
+          }
+          EXPECT in modifiers -> require(funSpec.body.isEmpty()) {
+            "functions in expect classes can't have bodies"
+          }
+        }
+      }
+
+      if (primaryConstructor == null) {
+        require(funSpecs.none { it.isConstructor } || superclassConstructorParameters.isEmpty()) {
+          "types without a primary constructor cannot specify secondary constructors and " +
+            "superclass constructor parameters"
+        }
+      }
+
+      if (isInlineOrValClass) {
+        primaryConstructor?.let {
+          check(it.parameters.size == 1) {
+            "value/inline classes must have 1 parameter in constructor"
+          }
+        }
+
+        check(propertySpecs.size > 0) {
+          "value/inline classes must have at least 1 property"
+        }
+
+        val constructorParamName = primaryConstructor?.parameters?.firstOrNull()?.name
+        constructorParamName?.let { paramName ->
+          val underlyingProperty = propertySpecs.find { it.name == paramName }
+          requireNotNull(underlyingProperty) {
+            "value/inline classes must have a single read-only (val) property parameter."
+          }
+          check(!underlyingProperty.mutable) {
+            "value/inline classes must have a single read-only (val) property parameter."
+          }
+        }
+        check(superclass == Any::class.asTypeName()) {
+          "value/inline classes cannot have super classes"
+        }
+      }
+
+      if (isFunInterface) {
+        // Note: Functional interfaces can contain any number of non-abstract functions.
+        val abstractFunSpecs = funSpecs.filter { ABSTRACT in it.modifiers }
+        check(abstractFunSpecs.size == 1) {
+          "Functional interfaces must have exactly one abstract function. Contained " +
+            "${abstractFunSpecs.size}: ${abstractFunSpecs.map { it.name }}"
+        }
+      }
+
+      when (typeSpecs.count { it.isCompanion }) {
+        0 -> Unit
+        1 -> {
+          require(isSimpleClass || kind == Kind.INTERFACE || isEnum || isAnnotation) {
+            "$kind types can't have a companion object"
+          }
+        }
+        else -> {
+          throw IllegalArgumentException("Multiple companion objects are present but only one is allowed.")
+        }
+      }
+
+      return TypeSpec(this)
+    }
+  }
+
+  public companion object {
+    @JvmStatic public fun classBuilder(name: String): Builder = Builder(Kind.CLASS, name)
+
+    @JvmStatic public fun classBuilder(className: ClassName): Builder =
+      classBuilder(className.simpleName)
+
+    @JvmStatic public fun expectClassBuilder(name: String): Builder =
+      Builder(Kind.CLASS, name, EXPECT)
+
+    @JvmStatic public fun expectClassBuilder(className: ClassName): Builder =
+      expectClassBuilder(className.simpleName)
+
+    @JvmStatic public fun valueClassBuilder(name: String): Builder =
+      Builder(Kind.CLASS, name, VALUE)
+
+    @JvmStatic public fun objectBuilder(name: String): Builder = Builder(Kind.OBJECT, name)
+
+    @JvmStatic public fun objectBuilder(className: ClassName): Builder =
+      objectBuilder(className.simpleName)
+
+    @JvmStatic @JvmOverloads
+    public fun companionObjectBuilder(name: String? = null): Builder =
+      Builder(Kind.OBJECT, name, COMPANION)
+
+    @JvmStatic public fun interfaceBuilder(name: String): Builder = Builder(Kind.INTERFACE, name)
+
+    @JvmStatic public fun interfaceBuilder(className: ClassName): Builder =
+      interfaceBuilder(className.simpleName)
+
+    @JvmStatic public fun funInterfaceBuilder(name: String): Builder =
+      Builder(Kind.INTERFACE, name, FUN)
+
+    @JvmStatic public fun funInterfaceBuilder(className: ClassName): Builder =
+      funInterfaceBuilder(className.simpleName)
+
+    @JvmStatic public fun enumBuilder(name: String): Builder = Builder(Kind.CLASS, name, ENUM)
+
+    @JvmStatic public fun enumBuilder(className: ClassName): Builder =
+      enumBuilder(className.simpleName)
+
+    @JvmStatic public fun anonymousClassBuilder(): Builder = Builder(Kind.CLASS, null)
+
+    @JvmStatic public fun annotationBuilder(name: String): Builder =
+      Builder(Kind.CLASS, name, ANNOTATION)
+
+    @JvmStatic public fun annotationBuilder(className: ClassName): Builder =
+      annotationBuilder(className.simpleName)
+  }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt
new file mode 100644
index 0000000..b2d7517
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+@file:JvmName("TypeVariableNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.util.Collections
+import javax.lang.model.element.TypeParameterElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.KVariance
+
+public class TypeVariableName private constructor(
+  public val name: String,
+  public val bounds: List<TypeName>,
+
+  /** Either [KModifier.IN], [KModifier.OUT], or null. */
+  public val variance: KModifier? = null,
+  public val isReified: Boolean = false,
+  nullable: Boolean = false,
+  annotations: List<AnnotationSpec> = emptyList(),
+  tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>,
+  ): TypeVariableName {
+    return copy(nullable, annotations, this.bounds, this.isReified, tags)
+  }
+
+  public fun copy(
+    nullable: Boolean = this.isNullable,
+    annotations: List<AnnotationSpec> = this.annotations.toList(),
+    bounds: List<TypeName> = this.bounds.toList(),
+    reified: Boolean = this.isReified,
+    tags: Map<KClass<*>, Any> = this.tagMap.tags,
+  ): TypeVariableName {
+    return TypeVariableName(
+      name,
+      bounds.withoutImplicitBound(),
+      variance,
+      reified,
+      nullable,
+      annotations,
+      tags,
+    )
+  }
+
+  private fun List<TypeName>.withoutImplicitBound(): List<TypeName> {
+    return if (size == 1) this else filterNot { it == NULLABLE_ANY }
+  }
+
+  override fun emit(out: CodeWriter) = out.emit(name)
+
+  public companion object {
+    internal fun of(
+      name: String,
+      bounds: List<TypeName>,
+      variance: KModifier?,
+    ): TypeVariableName {
+      require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) {
+        "$variance is an invalid variance modifier, the only allowed values are in and out!"
+      }
+      require(bounds.isNotEmpty()) {
+        "$name has no bounds"
+      }
+      // Strip Any? from bounds if it is present.
+      return TypeVariableName(name, bounds, variance)
+    }
+
+    /** Returns type variable named `name` with `variance` and without bounds. */
+    @JvmStatic
+    @JvmName("get")
+    @JvmOverloads
+    public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName =
+      of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance)
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("get")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      vararg bounds: TypeName,
+      variance: KModifier? = null,
+    ): TypeVariableName =
+      of(
+        name = name,
+        bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST),
+        variance = variance,
+      )
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("get")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      vararg bounds: KClass<*>,
+      variance: KModifier? = null,
+    ): TypeVariableName =
+      of(
+        name = name,
+        bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
+        variance = variance,
+      )
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("get")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      vararg bounds: Type,
+      variance: KModifier? = null,
+    ): TypeVariableName =
+      of(
+        name = name,
+        bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
+        variance = variance,
+      )
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("get")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      bounds: List<TypeName>,
+      variance: KModifier? = null,
+    ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance)
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("getWithClasses")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      bounds: Iterable<KClass<*>>,
+      variance: KModifier? = null,
+    ): TypeVariableName =
+      of(
+        name,
+        bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
+        variance,
+      )
+
+    /** Returns type variable named `name` with `variance` and `bounds`. */
+    @JvmStatic
+    @JvmName("getWithTypes")
+    @JvmOverloads
+    public operator fun invoke(
+      name: String,
+      bounds: Iterable<Type>,
+      variance: KModifier? = null,
+    ): TypeVariableName =
+      of(
+        name,
+        bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
+        variance,
+      )
+
+    /**
+     * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
+     * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a
+     * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables`
+     * map before looking up the bounds. Then if we encounter this TypeVariable again while
+     * constructing the bounds, we can just return it from the map. And, the code that put the entry
+     * in `variables` will make sure that the bounds are filled in before returning.
+     */
+    internal fun get(
+      mirror: javax.lang.model.type.TypeVariable,
+      typeVariables: MutableMap<TypeParameterElement, TypeVariableName>,
+    ): TypeVariableName {
+      val element = mirror.asElement() as TypeParameterElement
+      var typeVariableName: TypeVariableName? = typeVariables[element]
+      if (typeVariableName == null) {
+        // Since the bounds field is public, we need to make it an unmodifiableList. But we control
+        // the List that that wraps, which means we can change it before returning.
+        val bounds = mutableListOf<TypeName>()
+        val visibleBounds = Collections.unmodifiableList(bounds)
+        typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds)
+        typeVariables[element] = typeVariableName
+        for (typeMirror in element.bounds) {
+          bounds += get(typeMirror, typeVariables)
+        }
+        bounds.remove(ANY)
+        bounds.remove(JAVA_OBJECT)
+        if (bounds.isEmpty()) {
+          bounds.add(NULLABLE_ANY)
+        }
+      }
+      return typeVariableName
+    }
+
+    /** Returns type variable equivalent to `type`.  */
+    internal fun get(
+      type: java.lang.reflect.TypeVariable<*>,
+      map: MutableMap<Type, TypeVariableName> = mutableMapOf(),
+    ): TypeVariableName {
+      var result: TypeVariableName? = map[type]
+      if (result == null) {
+        val bounds = mutableListOf<TypeName>()
+        val visibleBounds = Collections.unmodifiableList(bounds)
+        result = TypeVariableName(type.name, visibleBounds)
+        map[type] = result
+        for (bound in type.bounds) {
+          bounds += get(bound, map)
+        }
+        bounds.remove(ANY)
+        bounds.remove(JAVA_OBJECT)
+        if (bounds.isEmpty()) {
+          bounds.add(NULLABLE_ANY)
+        }
+      }
+      return result
+    }
+
+    internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY)
+    private val JAVA_OBJECT = ClassName("java.lang", "Object")
+  }
+}
+
+/** Returns type variable equivalent to `mirror`. */
+@DelicateKotlinPoetApi(
+  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeVariable.asTypeVariableName(): TypeVariableName =
+  (asElement() as TypeParameterElement).asTypeVariableName()
+
+/** Returns type variable equivalent to `element`. */
+@DelicateKotlinPoetApi(
+  message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeParameterElement.asTypeVariableName(): TypeVariableName {
+  val name = simpleName.toString()
+  val boundsTypeNames = bounds.map(TypeMirror::asTypeName)
+    .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST)
+  return TypeVariableName.of(name, boundsTypeNames, variance = null)
+}
+
+public fun KTypeParameter.asTypeVariableName(): TypeVariableName {
+  return TypeVariableName.of(
+    name = name,
+    bounds = upperBounds.map(KType::asTypeName)
+      .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST),
+    variance = when (variance) {
+      KVariance.INVARIANT -> null
+      KVariance.IN -> KModifier.IN
+      KVariance.OUT -> KModifier.OUT
+    },
+  )
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt
new file mode 100644
index 0000000..16a443d
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.CodeBlock.Companion.isPlaceholder
+import java.util.Collections
+
+internal object NullAppendable : Appendable {
+  override fun append(charSequence: CharSequence) = this
+  override fun append(charSequence: CharSequence, start: Int, end: Int) = this
+  override fun append(c: Char) = this
+}
+
+internal fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
+  Collections.unmodifiableMap(LinkedHashMap(this))
+
+internal fun <T> Collection<T>.toImmutableList(): List<T> =
+  Collections.unmodifiableList(ArrayList(this))
+
+internal fun <T> Collection<T>.toImmutableSet(): Set<T> =
+  Collections.unmodifiableSet(LinkedHashSet(this))
+
+internal inline fun <reified T : Enum<T>> Collection<T>.toEnumSet(): Set<T> =
+  enumValues<T>().filterTo(mutableSetOf(), this::contains)
+
+internal fun requireNoneOrOneOf(modifiers: Set<KModifier>, vararg mutuallyExclusive: KModifier) {
+  val count = mutuallyExclusive.count(modifiers::contains)
+  require(count <= 1) {
+    "modifiers $modifiers must contain none or only one of ${mutuallyExclusive.contentToString()}"
+  }
+}
+
+internal fun requireNoneOf(modifiers: Set<KModifier>, vararg forbidden: KModifier) {
+  require(forbidden.none(modifiers::contains)) {
+    "modifiers $modifiers must contain none of ${forbidden.contentToString()}"
+  }
+}
+
+internal fun <T> T.isOneOf(t1: T, t2: T, t3: T? = null, t4: T? = null, t5: T? = null, t6: T? = null) =
+  this == t1 || this == t2 || this == t3 || this == t4 || this == t5 || this == t6
+
+internal fun <T> Collection<T>.containsAnyOf(vararg t: T) = t.any(this::contains)
+
+// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
+internal fun characterLiteralWithoutSingleQuotes(c: Char) = when {
+  c == '\b' -> "\\b" // \u0008: backspace (BS)
+  c == '\t' -> "\\t" // \u0009: horizontal tab (HT)
+  c == '\n' -> "\\n" // \u000a: linefeed (LF)
+  c == '\r' -> "\\r" // \u000d: carriage return (CR)
+  c == '\"' -> "\"" // \u0022: double quote (")
+  c == '\'' -> "\\'" // \u0027: single quote (')
+  c == '\\' -> "\\\\" // \u005c: backslash (\)
+  c.isIsoControl -> String.format("\\u%04x", c.code)
+  else -> c.toString()
+}
+
+internal fun escapeCharacterLiterals(s: String) = buildString {
+  for (c in s) append(characterLiteralWithoutSingleQuotes(c))
+}
+
+private val Char.isIsoControl: Boolean
+  get() {
+    return this in '\u0000'..'\u001F' || this in '\u007F'..'\u009F'
+  }
+
+/** Returns the string literal representing `value`, including wrapping double quotes.  */
+internal fun stringLiteralWithQuotes(
+  value: String,
+  isInsideRawString: Boolean = false,
+  isConstantContext: Boolean = false,
+): String {
+  if (!isConstantContext && '\n' in value) {
+    val result = StringBuilder(value.length + 32)
+    result.append("\"\"\"\n|")
+    var i = 0
+    while (i < value.length) {
+      val c = value[i]
+      if (value.regionMatches(i, "\"\"\"", 0, 3)) {
+        // Don't inadvertently end the raw string too early
+        result.append("\"\"\${'\"'}")
+        i += 2
+      } else if (c == '\n') {
+        // Add a '|' after newlines. This pipe will be removed by trimMargin().
+        result.append("\n|")
+      } else if (c == '$' && !isInsideRawString) {
+        // Escape '$' symbols with ${'$'}.
+        result.append("\${\'\$\'}")
+      } else {
+        result.append(c)
+      }
+      i++
+    }
+    // If the last-emitted character wasn't a margin '|', add a blank line. This will get removed
+    // by trimMargin().
+    if (!value.endsWith("\n")) result.append("\n")
+    result.append("\"\"\".trimMargin()")
+    return result.toString()
+  } else {
+    val result = StringBuilder(value.length + 32)
+    // using pre-formatted strings allows us to get away with not escaping symbols that would
+    // normally require escaping, e.g. "foo ${"bar"} baz"
+    if (isInsideRawString) result.append("\"\"\"") else result.append('"')
+    for (c in value) {
+      // Trivial case: single quote must not be escaped.
+      if (c == '\'') {
+        result.append("'")
+        continue
+      }
+      // Trivial case: double quotes must be escaped.
+      if (c == '\"' && !isInsideRawString) {
+        result.append("\\\"")
+        continue
+      }
+      // Trivial case: $ signs must be escaped.
+      if (c == '$' && !isInsideRawString) {
+        result.append("\${\'\$\'}")
+        continue
+      }
+      // Default case: just let character literal do its work.
+      result.append(if (isInsideRawString) c else characterLiteralWithoutSingleQuotes(c))
+      // Need to append indent after linefeed?
+    }
+    if (isInsideRawString) result.append("\"\"\"") else result.append('"')
+    return result.toString()
+  }
+}
+
+internal fun CodeBlock.ensureEndsWithNewLine() = if (isEmpty()) {
+  this
+} else {
+  with(toBuilder()) {
+    val lastFormatPart = trim().formatParts.last()
+    if (lastFormatPart.isPlaceholder && args.isNotEmpty()) {
+      val lastArg = args.last()
+      if (lastArg is String) {
+        args[args.size - 1] = lastArg.trimEnd('\n') + '\n'
+      }
+    } else {
+      formatParts[formatParts.lastIndexOf(lastFormatPart)] = lastFormatPart.trimEnd('\n')
+      formatParts += "\n"
+    }
+    return@with build()
+  }
+}
+
+private val IDENTIFIER_REGEX =
+  (
+    "((\\p{gc=Lu}+|\\p{gc=Ll}+|\\p{gc=Lt}+|\\p{gc=Lm}+|\\p{gc=Lo}+|\\p{gc=Nl}+)+" +
+      "\\d*" +
+      "\\p{gc=Lu}*\\p{gc=Ll}*\\p{gc=Lt}*\\p{gc=Lm}*\\p{gc=Lo}*\\p{gc=Nl}*)" +
+      "|" +
+      "(`[^\n\r`]+`)"
+    )
+    .toRegex()
+
+internal val String.isIdentifier get() = IDENTIFIER_REGEX.matches(this)
+
+// https://kotlinlang.org/docs/reference/keyword-reference.html
+private val KEYWORDS = setOf(
+  // Hard keywords
+  "as",
+  "break",
+  "class",
+  "continue",
+  "do",
+  "else",
+  "false",
+  "for",
+  "fun",
+  "if",
+  "in",
+  "interface",
+  "is",
+  "null",
+  "object",
+  "package",
+  "return",
+  "super",
+  "this",
+  "throw",
+  "true",
+  "try",
+  "typealias",
+  "typeof",
+  "val",
+  "var",
+  "when",
+  "while",
+
+  // Soft keywords
+  "by",
+  "catch",
+  "constructor",
+  "delegate",
+  "dynamic",
+  "field",
+  "file",
+  "finally",
+  "get",
+  "import",
+  "init",
+  "param",
+  "property",
+  "receiver",
+  "set",
+  "setparam",
+  "where",
+
+  // Modifier keywords
+  "actual",
+  "abstract",
+  "annotation",
+  "companion",
+  "const",
+  "crossinline",
+  "data",
+  "enum",
+  "expect",
+  "external",
+  "final",
+  "infix",
+  "inline",
+  "inner",
+  "internal",
+  "lateinit",
+  "noinline",
+  "open",
+  "operator",
+  "out",
+  "override",
+  "private",
+  "protected",
+  "public",
+  "reified",
+  "sealed",
+  "suspend",
+  "tailrec",
+  "value",
+  "vararg",
+
+  // These aren't keywords anymore but still break some code if unescaped. https://youtrack.jetbrains.com/issue/KT-52315
+  "header",
+  "impl",
+
+  // Other reserved keywords
+  "yield",
+)
+
+private const val ALLOWED_CHARACTER = '$'
+
+private const val UNDERSCORE_CHARACTER = '_'
+
+internal val String.isKeyword get() = this in KEYWORDS
+
+internal val String.hasAllowedCharacters get() = this.any { it == ALLOWED_CHARACTER }
+
+internal val String.allCharactersAreUnderscore get() = this.all { it == UNDERSCORE_CHARACTER }
+
+// https://github.com/JetBrains/kotlin/blob/master/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSimpleNameBacktickChecker.kt
+private val ILLEGAL_CHARACTERS_TO_ESCAPE = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\')
+
+private fun String.failIfEscapeInvalid() {
+  require(!any { it in ILLEGAL_CHARACTERS_TO_ESCAPE }) {
+    "Can't escape identifier $this because it contains illegal characters: " +
+      ILLEGAL_CHARACTERS_TO_ESCAPE.intersect(this.toSet()).joinToString("")
+  }
+}
+
+internal fun String.escapeIfNecessary(validate: Boolean = true): String = escapeIfNotJavaIdentifier()
+  .escapeIfKeyword()
+  .escapeIfHasAllowedCharacters()
+  .escapeIfAllCharactersAreUnderscore()
+  .apply { if (validate) failIfEscapeInvalid() }
+
+private fun String.alreadyEscaped() = startsWith("`") && endsWith("`")
+
+private fun String.escapeIfKeyword() = if (isKeyword && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfHasAllowedCharacters() = if (hasAllowedCharacters && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfAllCharactersAreUnderscore() = if (allCharactersAreUnderscore && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfNotJavaIdentifier(): String {
+  return if ((
+      !Character.isJavaIdentifierStart(first()) ||
+        drop(1).any { !Character.isJavaIdentifierPart(it) }
+      ) &&
+    !alreadyEscaped()
+  ) {
+    "`$this`".replace(' ', '·')
+  } else {
+    this
+  }
+}
+
+internal fun String.escapeSegmentsIfNecessary(delimiter: Char = '.') = split(delimiter)
+  .filter { it.isNotEmpty() }
+  .joinToString(delimiter.toString()) { it.escapeIfNecessary() }
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt
new file mode 100644
index 0000000..f7c4dc0
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+@file:JvmName("WildcardTypeNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.lang.reflect.WildcardType
+import javax.lang.model.element.TypeParameterElement
+import kotlin.reflect.KClass
+
+public class WildcardTypeName private constructor(
+  outTypes: List<TypeName>,
+  inTypes: List<TypeName>,
+  nullable: Boolean = false,
+  annotations: List<AnnotationSpec> = emptyList(),
+  tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+  public val outTypes: List<TypeName> = outTypes.toImmutableList()
+  public val inTypes: List<TypeName> = inTypes.toImmutableList()
+
+  init {
+    require(this.outTypes.size == 1) { "unexpected out types: $outTypes" }
+  }
+
+  override fun copy(
+    nullable: Boolean,
+    annotations: List<AnnotationSpec>,
+    tags: Map<KClass<*>, Any>,
+  ): WildcardTypeName {
+    return WildcardTypeName(outTypes, inTypes, nullable, annotations, tags)
+  }
+
+  override fun emit(out: CodeWriter): CodeWriter {
+    return when {
+      inTypes.size == 1 -> out.emitCode("in·%T", inTypes[0])
+      outTypes == STAR.outTypes -> out.emit("*")
+      else -> out.emitCode("out·%T", outTypes[0])
+    }
+  }
+
+  public companion object {
+    /**
+     * Returns a type that represents an unknown type that produces `outType`. For example, if
+     * `outType` is `CharSequence`, this returns `out CharSequence`. If `outType` is `Any?`, this
+     * returns `*`, which is shorthand for `out Any?`.
+     */
+    @JvmStatic public fun producerOf(outType: TypeName): WildcardTypeName =
+      WildcardTypeName(listOf(outType), emptyList())
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun producerOf(outType: Type): WildcardTypeName =
+      producerOf(outType.asTypeName())
+
+    @JvmStatic public fun producerOf(outType: KClass<*>): WildcardTypeName =
+      producerOf(outType.asTypeName())
+
+    /**
+     * Returns a type that represents an unknown type that consumes `inType`. For example, if
+     * `inType` is `String`, this returns `in String`.
+     */
+    @JvmStatic public fun consumerOf(inType: TypeName): WildcardTypeName =
+      WildcardTypeName(listOf(ANY), listOf(inType))
+
+    @DelicateKotlinPoetApi(
+      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+        "using the kotlinpoet-metadata APIs instead.",
+    )
+    @JvmStatic
+    public fun consumerOf(inType: Type): WildcardTypeName =
+      consumerOf(inType.asTypeName())
+
+    @JvmStatic public fun consumerOf(inType: KClass<*>): WildcardTypeName =
+      consumerOf(inType.asTypeName())
+
+    internal fun get(
+      mirror: javax.lang.model.type.WildcardType,
+      typeVariables: Map<TypeParameterElement, TypeVariableName>,
+    ): TypeName {
+      val outType = mirror.extendsBound
+      return if (outType == null) {
+        val inType = mirror.superBound
+        if (inType == null) {
+          STAR
+        } else {
+          consumerOf(get(inType, typeVariables))
+        }
+      } else {
+        producerOf(get(outType, typeVariables))
+      }
+    }
+
+    internal fun get(
+      wildcardName: WildcardType,
+      map: MutableMap<Type, TypeVariableName>,
+    ): TypeName {
+      return WildcardTypeName(
+        wildcardName.upperBounds.map { get(it, map = map) },
+        wildcardName.lowerBounds.map { get(it, map = map) },
+      )
+    }
+  }
+}
+
+@DelicateKotlinPoetApi(
+  message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun javax.lang.model.type.WildcardType.asWildcardTypeName(): TypeName =
+  WildcardTypeName.get(this, mutableMapOf())
+
+@DelicateKotlinPoetApi(
+  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+    " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun WildcardType.asWildcardTypeName(): TypeName =
+  WildcardTypeName.get(this, mutableMapOf())
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt
new file mode 100644
index 0000000..6f18620
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+@file:JvmName("JvmAnnotations")
+
+package com.squareup.kotlinpoet.jvm
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.FunSpec.Companion.isAccessor
+import com.squareup.kotlinpoet.FunSpec.Companion.isConstructor
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asTypeName
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+
+public fun FileSpec.Builder.jvmName(name: String): FileSpec.Builder = addAnnotation(
+  AnnotationSpec.builder(JvmName::class)
+    .useSiteTarget(FILE)
+    .addMember("%S", name)
+    .build(),
+)
+
+public fun FileSpec.Builder.jvmMultifileClass(): FileSpec.Builder = addAnnotation(
+  AnnotationSpec.builder(JvmMultifileClass::class)
+    .useSiteTarget(FILE)
+    .build(),
+)
+
+public fun TypeSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): TypeSpec.Builder =
+  addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+
+private fun jvmSuppressWildcardsAnnotation(suppress: Boolean = true) =
+  AnnotationSpec.builder(JvmSuppressWildcards::class)
+    .apply { if (!suppress) addMember("suppress = false") }
+    .build()
+
+public fun TypeSpec.Builder.jvmInline(): TypeSpec.Builder = addAnnotation(JvmInline::class)
+
+public fun TypeSpec.Builder.jvmRecord(): TypeSpec.Builder = addAnnotation(JvmRecord::class)
+
+public fun FunSpec.Builder.jvmStatic(): FunSpec.Builder = apply {
+  check(!name.isConstructor) { "Can't apply @JvmStatic to a constructor!" }
+  addAnnotation(JvmStatic::class)
+}
+
+public fun FunSpec.Builder.jvmOverloads(): FunSpec.Builder = apply {
+  check(!name.isAccessor) {
+    "Can't apply @JvmOverloads to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
+  }
+  addAnnotation(JvmOverloads::class)
+}
+
+public fun FunSpec.Builder.jvmName(name: String): FunSpec.Builder = apply {
+  check(!this.name.isConstructor) { "Can't apply @JvmName to a constructor!" }
+  addAnnotation(
+    AnnotationSpec.builder(JvmName::class)
+      .addMember("%S", name)
+      .build(),
+  )
+}
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: KClass<out Throwable>): FunSpec.Builder =
+  throws(exceptionClasses.map(KClass<*>::asTypeName))
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: Type): FunSpec.Builder =
+  throws(exceptionClasses.map(Type::asTypeName))
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: TypeName): FunSpec.Builder =
+  throws(exceptionClasses.toList())
+
+public fun FunSpec.Builder.throws(exceptionClasses: Iterable<TypeName>): FunSpec.Builder =
+  addAnnotation(
+    AnnotationSpec.builder(Throws::class)
+      .apply { exceptionClasses.forEach { addMember("%T::class", it) } }
+      .build(),
+  )
+
+public fun FunSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): FunSpec.Builder = apply {
+  check(!name.isConstructor) { "Can't apply @JvmSuppressWildcards to a constructor!" }
+  check(!name.isAccessor) {
+    "Can't apply @JvmSuppressWildcards to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
+  }
+  addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+}
+
+public fun FunSpec.Builder.synchronized(): FunSpec.Builder = apply {
+  check(!name.isConstructor) { "Can't apply @Synchronized to a constructor!" }
+  addAnnotation(Synchronized::class)
+}
+
+public fun FunSpec.Builder.strictfp(): FunSpec.Builder = addAnnotation(Strictfp::class)
+
+public fun PropertySpec.Builder.jvmField(): PropertySpec.Builder = addAnnotation(JvmField::class)
+
+public fun PropertySpec.Builder.jvmStatic(): PropertySpec.Builder = addAnnotation(JvmStatic::class)
+
+public fun PropertySpec.Builder.jvmSuppressWildcards(
+  suppress: Boolean = true,
+): PropertySpec.Builder = addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+
+public fun PropertySpec.Builder.transient(): PropertySpec.Builder = addAnnotation(Transient::class)
+
+public fun PropertySpec.Builder.volatile(): PropertySpec.Builder = addAnnotation(Volatile::class)
+
+public fun TypeName.jvmSuppressWildcards(suppress: Boolean = true): TypeName =
+  copy(annotations = this.annotations + jvmSuppressWildcardsAnnotation(suppress))
+
+public fun TypeName.jvmWildcard(): TypeName =
+  copy(annotations = this.annotations + AnnotationSpec.builder(JvmWildcard::class).build())
+
+@Suppress("DEPRECATION")
+@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
+public fun PropertySpec.Builder.jvmDefault(): PropertySpec.Builder =
+  addAnnotation(JvmDefault::class)
+
+@Suppress("DEPRECATION")
+@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
+public fun FunSpec.Builder.jvmDefault(): FunSpec.Builder = addAnnotation(JvmDefault::class)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt
new file mode 100644
index 0000000..22be037
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet.tags
+
+import com.squareup.kotlinpoet.TypeName
+
+/**
+ * This tag indicates that this [TypeName] represents a `typealias` type.
+ *
+ * @property [abbreviatedType] the underlying type for this alias.
+ */
+public class TypeAliasTag(public val abbreviatedType: TypeName)
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt
new file mode 100644
index 0000000..b78a96e
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 Google, 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Serializable
+import java.nio.charset.Charset
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVisitor
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+import kotlin.reflect.full.createType
+import kotlin.reflect.full.declaredFunctions
+import kotlin.reflect.full.starProjectedType
+import kotlin.reflect.full.withNullability
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.fail
+
+abstract class AbstractTypesTest {
+  protected abstract val elements: Elements
+  protected abstract val types: Types
+
+  private fun getElement(`class`: Class<*>) = elements.getTypeElement(`class`.canonicalName)
+
+  private fun getMirror(`class`: Class<*>) = getElement(`class`).asType()
+
+  @Test fun getBasicTypeMirror() {
+    assertThat(getMirror(Any::class.java).asTypeName())
+      .isEqualTo(Any::class.java.asClassName())
+    assertThat(getMirror(Charset::class.java).asTypeName())
+      .isEqualTo(Charset::class.asClassName())
+    assertThat(getMirror(AbstractTypesTest::class.java).asTypeName())
+      .isEqualTo(AbstractTypesTest::class.asClassName())
+  }
+
+  @Test fun getParameterizedTypeMirror() {
+    val setType = types.getDeclaredType(getElement(Set::class.java), getMirror(String::class.java))
+    assertThat(setType.asTypeName())
+      .isEqualTo(Set::class.asClassName().parameterizedBy(String::class.asClassName()))
+  }
+
+  @Test fun getErrorType() {
+    val errorType = DeclaredTypeAsErrorType(types.getDeclaredType(getElement(Set::class.java)))
+    assertThat(errorType.asTypeName()).isEqualTo(Set::class.asClassName())
+  }
+
+  internal class Parameterized<
+    Simple,
+    ExtendsClass : Number,
+    ExtendsInterface : Runnable,
+    ExtendsTypeVariable : Simple,
+    Intersection,
+    IntersectionOfInterfaces>
+  where IntersectionOfInterfaces : Runnable, Intersection : Number, Intersection : Runnable,
+        IntersectionOfInterfaces : Serializable
+
+  @Test fun getTypeVariableTypeMirror() {
+    val typeVariables = getElement(Parameterized::class.java).typeParameters
+
+    // Members of converted types use ClassName and not Class<?>.
+    val number = Number::class.asClassName()
+    val runnable = Runnable::class.asClassName()
+    val serializable = Serializable::class.asClassName()
+
+    assertThat(typeVariables[0].asType().asTypeName())
+      .isEqualTo(TypeVariableName("Simple"))
+    assertThat(typeVariables[1].asType().asTypeName())
+      .isEqualTo(TypeVariableName("ExtendsClass", number))
+    assertThat(typeVariables[2].asType().asTypeName())
+      .isEqualTo(TypeVariableName("ExtendsInterface", runnable))
+    assertThat(typeVariables[3].asType().asTypeName())
+      .isEqualTo(TypeVariableName("ExtendsTypeVariable", TypeVariableName("Simple")))
+    assertThat(typeVariables[4].asType().asTypeName())
+      .isEqualTo(TypeVariableName("Intersection", number, runnable))
+    assertThat(typeVariables[5].asType().asTypeName())
+      .isEqualTo(TypeVariableName("IntersectionOfInterfaces", runnable, serializable))
+    assertThat((typeVariables[4].asType().asTypeName() as TypeVariableName).bounds)
+      .containsExactly(number, runnable)
+  }
+
+  internal class Recursive<T : Map<List<T>, Set<Array<T>>>>
+
+  @Test fun getTypeVariableTypeMirrorRecursive() {
+    val typeMirror = getElement(Recursive::class.java).asType()
+    val typeName = typeMirror.asTypeName() as ParameterizedTypeName
+    val className = Recursive::class.java.canonicalName
+    assertThat(typeName.toString()).isEqualTo("$className<T>")
+
+    val typeVariableName = typeName.typeArguments[0] as TypeVariableName
+    assertThat(typeVariableName.toString()).isEqualTo("T")
+    assertThat(typeVariableName.bounds.toString())
+      .isEqualTo("[kotlin.collections.Map<kotlin.collections.List<out T>, out kotlin.collections.Set<out kotlin.Array<T>>>]")
+  }
+
+  @Test fun getPrimitiveTypeMirror() {
+    assertThat(types.getPrimitiveType(TypeKind.BOOLEAN).asTypeName()).isEqualTo(BOOLEAN)
+    assertThat(types.getPrimitiveType(TypeKind.BYTE).asTypeName()).isEqualTo(BYTE)
+    assertThat(types.getPrimitiveType(TypeKind.SHORT).asTypeName()).isEqualTo(SHORT)
+    assertThat(types.getPrimitiveType(TypeKind.INT).asTypeName()).isEqualTo(INT)
+    assertThat(types.getPrimitiveType(TypeKind.LONG).asTypeName()).isEqualTo(LONG)
+    assertThat(types.getPrimitiveType(TypeKind.CHAR).asTypeName()).isEqualTo(CHAR)
+    assertThat(types.getPrimitiveType(TypeKind.FLOAT).asTypeName()).isEqualTo(FLOAT)
+    assertThat(types.getPrimitiveType(TypeKind.DOUBLE).asTypeName()).isEqualTo(DOUBLE)
+  }
+
+  @Test fun getArrayTypeMirror() {
+    assertThat(types.getArrayType(getMirror(String::class.java)).asTypeName())
+      .isEqualTo(ARRAY.parameterizedBy(String::class.asClassName()))
+  }
+
+  @Test fun getVoidTypeMirror() {
+    assertThat(types.getNoType(TypeKind.VOID).asTypeName()).isEqualTo(UNIT)
+  }
+
+  @Test fun getNullTypeMirror() {
+    try {
+      types.nullType.asTypeName()
+      fail()
+    } catch (expected: IllegalArgumentException) {
+    }
+  }
+
+  @Test fun parameterizedType() {
+    val type = Map::class.parameterizedBy(String::class, Long::class)
+    assertThat(type.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Long>")
+  }
+
+  @Test fun starProjection() {
+    assertThat(STAR.toString()).isEqualTo("*")
+  }
+
+  @Ignore("Figure out what this maps to in Kotlin.")
+  @Test fun starProjectionFromMirror() {
+    val wildcard = types.getWildcardType(null, null)
+    val type = wildcard.asTypeName()
+    assertThat(type.toString()).isEqualTo("*")
+  }
+
+  @Test fun varianceOutType() {
+    val type = WildcardTypeName.producerOf(CharSequence::class)
+    assertThat(type.toString()).isEqualTo("out java.lang.CharSequence")
+  }
+
+  @Test fun varianceOutTypeFromMirror() {
+    val types = types
+    val elements = elements
+    val charSequence = elements.getTypeElement(CharSequence::class.java.name).asType()
+    val wildcard = types.getWildcardType(charSequence, null)
+    val type = wildcard.asTypeName()
+    assertThat(type.toString()).isEqualTo("out java.lang.CharSequence")
+  }
+
+  @Test fun varianceInType() {
+    val type = WildcardTypeName.consumerOf(String::class)
+    assertThat(type.toString()).isEqualTo("in kotlin.String")
+  }
+
+  @Test fun varianceInTypeFromMirror() {
+    val types = types
+    val elements = elements
+    val string = elements.getTypeElement(String::class.java.name).asType()
+    val wildcard = types.getWildcardType(null, string)
+    val type = wildcard.asTypeName()
+    assertThat(type.toString()).isEqualTo("in kotlin.String")
+  }
+
+  @Test fun typeVariable() {
+    val type = TypeVariableName("T", CharSequence::class)
+    assertThat(type.toString()).isEqualTo("T") // (Bounds are only emitted in declaration.)
+  }
+
+  @Test fun kType() {
+    assertThat(Map::class.starProjectedType.asTypeName().toString())
+      .isEqualTo("kotlin.collections.Map<*, *>")
+    assertThat(Map::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, String::class.createType(emptyList())), KTypeProjection.STAR)).asTypeName().toString())
+      .isEqualTo("kotlin.collections.Map<kotlin.String, *>")
+    assertThat(Map.Entry::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, String::class.createType(emptyList())), KTypeProjection.STAR)).asTypeName().toString())
+      .isEqualTo("kotlin.collections.Map.Entry<kotlin.String, *>")
+    assertThat(Any::class.starProjectedType.withNullability(true).asTypeName().toString())
+      .isEqualTo("kotlin.Any?")
+
+    val treeMapClass = java.util.TreeMap::class
+    assertThat(treeMapClass.declaredFunctions.find { it.name == "parentOf" }!!.returnType.asTypeName().toString())
+      .isEqualTo("java.util.TreeMap.Entry<K, V>")
+  }
+
+  private class DeclaredTypeAsErrorType(private val declaredType: DeclaredType) : ErrorType {
+    override fun asElement(): Element = declaredType.asElement()
+
+    override fun getEnclosingType(): TypeMirror = declaredType.enclosingType
+
+    override fun getTypeArguments(): MutableList<out TypeMirror> = declaredType.typeArguments
+
+    override fun getKind(): TypeKind = declaredType.kind
+
+    override fun <R, P> accept(typeVisitor: TypeVisitor<R, P>, p: P): R = typeVisitor.visitError(this, p)
+
+    override fun <A : Annotation> getAnnotationsByType(annotationType: Class<A>): Array<A> =
+      throw UnsupportedOperationException()
+
+    override fun <A : Annotation> getAnnotation(annotationType: Class<A>): A = throw UnsupportedOperationException()
+
+    override fun getAnnotationMirrors() = throw UnsupportedOperationException()
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt
new file mode 100644
index 0000000..3e7b766
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+class AnnotatedTypeNameTest {
+  private val NEVER_NULL = AnnotationSpec.builder(NeverNull::class).build()
+  private val NN = NeverNull::class.java.canonicalName
+
+  annotation class NeverNull
+
+  @Test fun annotated() {
+    val simpleString = String::class.asTypeName()
+    assertFalse(simpleString.isAnnotated)
+    assertEquals(simpleString, String::class.asTypeName())
+    val annotated = simpleString.copy(annotations = simpleString.annotations + NEVER_NULL)
+    assertTrue(annotated.isAnnotated)
+  }
+
+  @Test fun annotatedType() {
+    val expected = "@$NN kotlin.String"
+    val type = String::class.asTypeName()
+    val actual = type.copy(annotations = type.annotations + NEVER_NULL).toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedTwice() {
+    val expected = "@$NN @java.lang.Override kotlin.String"
+    val type = String::class.asTypeName()
+    val actual = type
+      .copy(
+        annotations = type.annotations + NEVER_NULL +
+          AnnotationSpec.builder(Override::class).build(),
+      )
+      .toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedParameterizedType() {
+    val expected = "@$NN kotlin.collections.List<kotlin.String>"
+    val type = List::class.parameterizedBy(String::class)
+    val actual = type.copy(annotations = type.annotations + NEVER_NULL).toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedArgumentOfParameterizedType() {
+    val expected = "kotlin.collections.List<@$NN kotlin.String>"
+    val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+    val list = List::class.asClassName()
+    val actual = list.parameterizedBy(type).toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedWildcardTypeNameWithSuper() {
+    val expected = "in @$NN kotlin.String"
+    val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+    val actual = WildcardTypeName.consumerOf(type).toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedWildcardTypeNameWithExtends() {
+    val expected = "out @$NN kotlin.String"
+    val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+    val actual = WildcardTypeName.producerOf(type).toString()
+    assertEquals(expected, actual)
+  }
+
+  @Test fun annotatedEquivalence() {
+    annotatedEquivalence(UNIT)
+    annotatedEquivalence(Any::class.asClassName())
+    annotatedEquivalence(List::class.parameterizedBy(Any::class))
+    annotatedEquivalence(TypeVariableName("A"))
+    annotatedEquivalence(WildcardTypeName.producerOf(Object::class))
+  }
+
+  private fun annotatedEquivalence(type: TypeName) {
+    assertFalse(type.isAnnotated)
+    assertEquals(type, type)
+    assertEquals(
+      type.copy(annotations = listOf(NEVER_NULL)),
+      type.copy(annotations = listOf(NEVER_NULL)),
+    )
+    assertNotEquals(type, type.copy(annotations = listOf(NEVER_NULL)))
+    assertEquals(type.hashCode().toLong(), type.hashCode().toLong())
+    assertEquals(
+      type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+      type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+    )
+    assertNotEquals(
+      type.hashCode().toLong(),
+      type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+    )
+  }
+
+  // https://github.com/square/javapoet/issues/431
+  // @Target(ElementType.TYPE_USE) requires Java 1.8
+  annotation class TypeUseAnnotation
+
+  // https://github.com/square/javapoet/issues/431
+  @Ignore @Test
+  fun annotatedNestedType() {
+    val expected = "kotlin.collections.Map.@" + TypeUseAnnotation::class.java.canonicalName + " Entry"
+    val typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation::class).build()
+    val type = Map.Entry::class.asTypeName().copy(annotations = listOf(typeUseAnnotation))
+    val actual = type.toString()
+    assertEquals(expected, actual)
+  }
+
+  // https://github.com/square/javapoet/issues/431
+  @Ignore @Test
+  fun annotatedNestedParameterizedType() {
+    val expected = "kotlin.collections.Map.@" + TypeUseAnnotation::class.java.canonicalName +
+      " Entry<kotlin.Byte, kotlin.Byte>"
+    val typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation::class).build()
+    val type = Map.Entry::class.parameterizedBy(Byte::class, Byte::class)
+      .copy(annotations = listOf(typeUseAnnotation))
+    val actual = type.toString()
+    assertEquals(expected, actual)
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt
new file mode 100644
index 0000000..4f77040
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.KModifier.OVERRIDE
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
+import java.lang.annotation.Inherited
+import kotlin.reflect.KClass
+import kotlin.test.Test
+import org.junit.Rule
+
+class AnnotationSpecTest {
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class AnnotationA
+
+  @Inherited
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class AnnotationB
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class AnnotationC(val value: String)
+
+  enum class Breakfast {
+    WAFFLES, PANCAKES;
+
+    override fun toString(): String {
+      return "$name with cherries!"
+    }
+  }
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class HasDefaultsAnnotation(
+    val a: Byte = 5,
+    val b: Short = 6,
+    val c: Int = 7,
+    val d: Long = 8,
+    val e: Float = 9.0f,
+    val f: Double = 10.0,
+    val g: CharArray = ['\u0000', '\uCAFE', 'z', '€', 'ℕ', '"', '\'', '\t', '\n'],
+    val h: Boolean = true,
+    val i: Breakfast = Breakfast.WAFFLES,
+    val j: AnnotationA = AnnotationA(),
+    val k: String = "maple",
+    val l: KClass<out Annotation> = AnnotationB::class,
+    val m: IntArray = [1, 2, 3],
+    val n: Array<Breakfast> = [Breakfast.WAFFLES, Breakfast.PANCAKES],
+    val o: Breakfast,
+    val p: Int,
+    val q: AnnotationC = AnnotationC("foo"),
+    val r: Array<KClass<out Number>> = [Byte::class, Short::class, Int::class, Long::class],
+  )
+
+  @HasDefaultsAnnotation(
+    o = Breakfast.PANCAKES,
+    p = 1701,
+    f = 11.1,
+    m = [9, 8, 1],
+    l = Override::class,
+    j = AnnotationA(),
+    q = AnnotationC("bar"),
+    r = [Float::class, Double::class],
+  )
+  inner class IsAnnotated
+
+  @Rule @JvmField
+  val compilation = CompilationRule()
+
+  @Test fun equalsAndHashCode() {
+    var a = AnnotationSpec.builder(AnnotationC::class.java).build()
+    var b = AnnotationSpec.builder(AnnotationC::class.java).build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = AnnotationSpec.builder(AnnotationC::class.java).addMember("value", "%S", "123").build()
+    b = AnnotationSpec.builder(AnnotationC::class.java).addMember("value", "%S", "123").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun defaultAnnotation() {
+    val name = IsAnnotated::class.java.canonicalName
+    val element = compilation.elements.getTypeElement(name)
+    val annotation = AnnotationSpec.get(element.annotationMirrors[0])
+
+    assertThat(toString(annotation)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.AnnotationSpecTest
+        |import java.lang.Override
+        |import kotlin.Double
+        |import kotlin.Float
+        |
+        |@AnnotationSpecTest.HasDefaultsAnnotation(
+        |  f = 11.1,
+        |  j = AnnotationSpecTest.AnnotationA(),
+        |  l = Override::class,
+        |  m = arrayOf(9, 8, 1),
+        |  o = AnnotationSpecTest.Breakfast.PANCAKES,
+        |  p = 1_701,
+        |  q = AnnotationSpecTest.AnnotationC(value = "bar"),
+        |  r = arrayOf(Float::class, Double::class),
+        |)
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun defaultAnnotationWithImport() {
+    val name = IsAnnotated::class.java.canonicalName
+    val element = compilation.elements.getTypeElement(name)
+    val annotation = AnnotationSpec.get(element.annotationMirrors[0])
+    val typeBuilder = TypeSpec.classBuilder(IsAnnotated::class.java.simpleName)
+    typeBuilder.addAnnotation(annotation)
+    val file = FileSpec.get("com.squareup.kotlinpoet", typeBuilder.build())
+    assertThat(file.toString()).isEqualTo(
+      """
+        |package com.squareup.kotlinpoet
+        |
+        |import java.lang.Override
+        |import kotlin.Double
+        |import kotlin.Float
+        |
+        |@AnnotationSpecTest.HasDefaultsAnnotation(
+        |  f = 11.1,
+        |  j = AnnotationSpecTest.AnnotationA(),
+        |  l = Override::class,
+        |  m = arrayOf(9, 8, 1),
+        |  o = AnnotationSpecTest.Breakfast.PANCAKES,
+        |  p = 1_701,
+        |  q = AnnotationSpecTest.AnnotationC(value = "bar"),
+        |  r = arrayOf(Float::class, Double::class),
+        |)
+        |public class IsAnnotated
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun emptyArray() {
+    val builder = AnnotationSpec.builder(HasDefaultsAnnotation::class.java)
+    builder.addMember("%L = %L", "n", "[]")
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@com.squareup.kotlinpoet.AnnotationSpecTest.HasDefaultsAnnotation(" +
+        "n = []" +
+        ")",
+    )
+    builder.addMember("%L = %L", "m", "[]")
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@com.squareup.kotlinpoet.AnnotationSpecTest.HasDefaultsAnnotation(" +
+        "n = [], " +
+        "m = []" +
+        ")",
+    )
+  }
+
+  @Test fun reflectAnnotation() {
+    val annotation = IsAnnotated::class.java.getAnnotation(HasDefaultsAnnotation::class.java)
+    val spec = AnnotationSpec.get(annotation)
+
+    assertThat(toString(spec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.AnnotationSpecTest
+        |import java.lang.Override
+        |import kotlin.Double
+        |import kotlin.Float
+        |
+        |@AnnotationSpecTest.HasDefaultsAnnotation(
+        |  f = 11.1,
+        |  l = Override::class,
+        |  m = arrayOf(9, 8, 1),
+        |  o = AnnotationSpecTest.Breakfast.PANCAKES,
+        |  p = 1_701,
+        |  q = AnnotationSpecTest.AnnotationC(value = "bar"),
+        |  r = arrayOf(Float::class, Double::class),
+        |)
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun reflectAnnotationWithDefaults() {
+    val annotation = IsAnnotated::class.java.getAnnotation(HasDefaultsAnnotation::class.java)
+    val spec = AnnotationSpec.get(annotation, true)
+
+    assertThat(toString(spec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.AnnotationSpecTest
+        |import java.lang.Override
+        |import kotlin.Double
+        |import kotlin.Float
+        |
+        |@AnnotationSpecTest.HasDefaultsAnnotation(
+        |  a = 5,
+        |  b = 6,
+        |  c = 7,
+        |  d = 8,
+        |  e = 9.0f,
+        |  f = 11.1,
+        |  g = arrayOf('\u0000', '쫾', 'z', '€', 'ℕ', '"', '\'', '\t', '\n'),
+        |  h = true,
+        |  i = AnnotationSpecTest.Breakfast.WAFFLES,
+        |  j = AnnotationSpecTest.AnnotationA(),
+        |  k = "maple",
+        |  l = Override::class,
+        |  m = arrayOf(9, 8, 1),
+        |  n = arrayOf(AnnotationSpecTest.Breakfast.WAFFLES, AnnotationSpecTest.Breakfast.PANCAKES),
+        |  o = AnnotationSpecTest.Breakfast.PANCAKES,
+        |  p = 1_701,
+        |  q = AnnotationSpecTest.AnnotationC(value = "bar"),
+        |  r = arrayOf(Float::class, Double::class),
+        |)
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun useSiteTarget() {
+    val builder = AnnotationSpec.builder(AnnotationA::class)
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+    )
+    builder.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@field:com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+    )
+    builder.useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@get:com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+    )
+    builder.useSiteTarget(null)
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+    )
+  }
+
+  @Test fun deprecatedTest() {
+    val annotation = AnnotationSpec.builder(Deprecated::class)
+      .addMember("%S", "Nope")
+      .addMember("%T(%S)", ReplaceWith::class, "Yep")
+      .build()
+
+    assertThat(annotation.toString()).isEqualTo(
+      "" +
+        "@kotlin.Deprecated(\"Nope\", kotlin.ReplaceWith(\"Yep\"))",
+    )
+  }
+
+  @Test fun modifyMembers() {
+    val builder = AnnotationSpec.builder(Deprecated::class)
+      .addMember("%S", "Nope")
+      .addMember("%T(%S)", ReplaceWith::class, "Yep")
+
+    builder.members.removeAt(1)
+    builder.members.add(CodeBlock.of("%T(%S)", ReplaceWith::class, "Nope"))
+
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@kotlin.Deprecated(\"Nope\", kotlin.ReplaceWith(\"Nope\"))",
+    )
+  }
+
+  @Test fun annotationStringsAreConstant() {
+    val text = "This is a long string with a newline\nin the middle."
+    val builder = AnnotationSpec.builder(Deprecated::class)
+      .addMember("%S", text)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      "" +
+        "@kotlin.Deprecated(\"This is a long string with a newline\\nin the middle.\")",
+    )
+  }
+
+  @Test fun literalAnnotation() {
+    val annotationSpec = AnnotationSpec.builder(Suppress::class)
+      .addMember("%S", "Things")
+      .build()
+
+    val file = FileSpec.builder("test", "Test")
+      .addFunction(
+        FunSpec.builder("test")
+          .addStatement("%L", annotationSpec)
+          .addStatement("val annotatedString = %S", "AnnotatedString")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString().trim()).isEqualTo(
+      """
+      |package test
+      |
+      |import kotlin.Suppress
+      |import kotlin.Unit
+      |
+      |public fun test(): Unit {
+      |  @Suppress("Things")
+      |  val annotatedString = "AnnotatedString"
+      |}
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionOnlyLiteralAnnotation() {
+    val annotation = AnnotationSpec
+      .builder(ClassName.bestGuess("Suppress"))
+      .addMember("%S", "UNCHECKED_CAST")
+      .build()
+    val funSpec = FunSpec.builder("operation")
+      .addStatement("%L", annotation)
+      .build()
+
+    assertThat(funSpec.toString().trim()).isEqualTo(
+      """
+      |public fun operation(): kotlin.Unit {
+      |  @Suppress("UNCHECKED_CAST")
+      |}
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getOnValueArrayTypeMirrorShouldNameValueArg() {
+    val myClazz = compilation.elements
+      .getTypeElement(JavaClassWithArrayValueAnnotation::class.java.canonicalName)
+    val classBuilder = TypeSpec.classBuilder("Result")
+
+    myClazz.annotationMirrors.map { AnnotationSpec.get(it) }
+      .forEach {
+        classBuilder.addAnnotation(it)
+      }
+
+    assertThat(toString(classBuilder.build())).isEqualTo(
+      """
+            |package com.squareup.tacos
+            |
+            |import com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation
+            |import java.lang.Boolean
+            |import java.lang.Object
+            |
+            |@JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue(value = arrayOf(Object::class,
+            |        Boolean::class))
+            |public class Result
+            |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getOnVarargMirrorShouldNameValueArg() {
+    val myClazz = compilation.elements
+      .getTypeElement(KotlinClassWithVarargAnnotation::class.java.canonicalName)
+    val classBuilder = TypeSpec.classBuilder("Result")
+
+    myClazz.annotationMirrors.map { AnnotationSpec.get(it) }
+      .filter {
+        val typeName = it.typeName
+        return@filter typeName is ClassName && typeName.simpleName == "AnnotationWithArrayValue"
+      }
+      .forEach {
+        classBuilder.addAnnotation(it)
+      }
+
+    assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.AnnotationSpecTest
+        |import java.lang.Object
+        |import kotlin.Boolean
+        |
+        |@AnnotationSpecTest.AnnotationWithArrayValue(value = arrayOf(Object::class, Boolean::class))
+        |public class Result
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getOnValueArrayTypeAnnotationShouldNameValueArg() {
+    val annotation = JavaClassWithArrayValueAnnotation::class.java.getAnnotation(
+      JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue::class.java,
+    )
+    val classBuilder = TypeSpec.classBuilder("Result")
+      .addAnnotation(AnnotationSpec.get(annotation))
+
+    assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation
+        |import java.lang.Boolean
+        |import java.lang.Object
+        |
+        |@JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue(value = arrayOf(Object::class,
+        |        Boolean::class))
+        |public class Result
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getOnVarargAnnotationShouldNameValueArg() {
+    val annotation = KotlinClassWithVarargAnnotation::class.java
+      .getAnnotation(AnnotationWithArrayValue::class.java)
+    val classBuilder = TypeSpec.classBuilder("Result")
+      .addAnnotation(AnnotationSpec.get(annotation))
+
+    assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.kotlinpoet.AnnotationSpecTest
+        |import java.lang.Object
+        |import kotlin.Boolean
+        |
+        |@AnnotationSpecTest.AnnotationWithArrayValue(value = arrayOf(Object::class, Boolean::class))
+        |public class Result
+      """.trimMargin(),
+    )
+  }
+
+  @AnnotationWithArrayValue(Any::class, Boolean::class)
+  class KotlinClassWithVarargAnnotation
+
+  @Retention(AnnotationRetention.RUNTIME)
+  internal annotation class AnnotationWithArrayValue(vararg val value: KClass<*>)
+
+  @Test fun annotationsWithTypeParameters() {
+    // Example from https://kotlinlang.org/docs/tutorials/android-plugin.html
+    val externalClass = ClassName("com.squareup.parceler", "ExternalClass")
+    val externalClassSpec = TypeSpec.classBuilder(externalClass)
+      .addProperty(
+        PropertySpec.builder("value", Int::class)
+          .initializer("value")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("value", Int::class)
+          .build(),
+      )
+      .build()
+    val externalClassParceler = ClassName("com.squareup.parceler", "ExternalClassParceler")
+    val parcel = ClassName("com.squareup.parceler", "Parcel")
+    val externalClassParcelerSpec = TypeSpec.objectBuilder(externalClassParceler)
+      .addSuperinterface(
+        ClassName("com.squareup.parceler", "Parceler")
+          .parameterizedBy(externalClass),
+      )
+      .addFunction(
+        FunSpec.builder("create")
+          .addModifiers(OVERRIDE)
+          .addParameter("parcel", parcel)
+          .addStatement("return %T(parcel.readInt())", externalClass)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("write")
+          .addModifiers(OVERRIDE)
+          .receiver(externalClass)
+          .addParameter("parcel", parcel)
+          .addParameter("flags", Int::class)
+          .addStatement("parcel.writeInt(value)")
+          .build(),
+      )
+      .build()
+    val parcelize = ClassName("com.squareup.parceler", "Parcelize")
+    val typeParceler = ClassName("com.squareup.parceler", "TypeParceler")
+    val typeParcelerAnnotation = AnnotationSpec.builder(
+      typeParceler
+        .plusParameter(externalClass)
+        .plusParameter(externalClassParceler),
+    )
+      .build()
+    val classLocalParceler = TypeSpec.classBuilder("MyClass")
+      .addAnnotation(parcelize)
+      .addAnnotation(typeParcelerAnnotation)
+      .addProperty(
+        PropertySpec.builder("external", externalClass)
+          .initializer("external")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("external", externalClass)
+          .build(),
+      )
+      .build()
+    val propertyLocalParceler = TypeSpec.classBuilder("MyClass")
+      .addAnnotation(parcelize)
+      .addProperty(
+        PropertySpec.builder("external", externalClass)
+          .addAnnotation(typeParcelerAnnotation)
+          .initializer("external")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("external", externalClass)
+          .build(),
+      )
+      .build()
+    val writeWith = ClassName("com.squareup.parceler", "WriteWith")
+    val writeWithExternalClass = externalClass
+      .copy(
+        annotations = listOf(
+          AnnotationSpec
+            .builder(writeWith.plusParameter(externalClassParceler))
+            .build(),
+        ),
+      )
+    val typeLocalParceler = TypeSpec.classBuilder("MyClass")
+      .addAnnotation(parcelize)
+      .addProperty(
+        PropertySpec.builder("external", writeWithExternalClass)
+          .initializer("external")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("external", writeWithExternalClass)
+          .build(),
+      )
+      .build()
+    val file = FileSpec.builder("com.squareup.parceler", "Test")
+      .addType(externalClassSpec)
+      .addType(externalClassParcelerSpec)
+      .addType(classLocalParceler)
+      .addType(propertyLocalParceler)
+      .addType(typeLocalParceler)
+      .build()
+    //language=kotlin
+    assertThat(file.toString()).isEqualTo(
+      """
+      package com.squareup.parceler
+
+      import kotlin.Int
+      import kotlin.Unit
+
+      public class ExternalClass(
+        public val `value`: Int,
+      )
+
+      public object ExternalClassParceler : Parceler<ExternalClass> {
+        public override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
+
+        public override fun ExternalClass.write(parcel: Parcel, flags: Int): Unit {
+          parcel.writeInt(value)
+        }
+      }
+
+      @Parcelize
+      @TypeParceler<ExternalClass, ExternalClassParceler>
+      public class MyClass(
+        public val `external`: ExternalClass,
+      )
+
+      @Parcelize
+      public class MyClass(
+        @TypeParceler<ExternalClass, ExternalClassParceler>
+        public val `external`: ExternalClass,
+      )
+
+      @Parcelize
+      public class MyClass(
+        public val `external`: @WriteWith<ExternalClassParceler> ExternalClass,
+      )
+
+      """.trimIndent(),
+    )
+  }
+
+  private fun toString(annotationSpec: AnnotationSpec) =
+    toString(TypeSpec.classBuilder("Taco").addAnnotation(annotationSpec).build())
+
+  private fun toString(typeSpec: TypeSpec) =
+    FileSpec.get("com.squareup.tacos", typeSpec).toString()
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt
new file mode 100644
index 0000000..facd702
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.ThrowableSubject
+import com.google.common.truth.Truth.assertThat
+
+inline fun <reified T> assertThrows(block: () -> Unit): ThrowableSubject {
+  try {
+    block()
+  } catch (e: Throwable) {
+    if (e is T) {
+      return assertThat(e)
+    } else {
+      throw e
+    }
+  }
+  throw AssertionError("Expected ${T::class.simpleName}")
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt
new file mode 100644
index 0000000..6d06806
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 Google, 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import org.junit.Rule
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ClassNameTest {
+  @Rule @JvmField var compilationRule = CompilationRule()
+
+  @Test fun bestGuessForString_simpleClass() {
+    assertThat(ClassName.bestGuess(String::class.java.name))
+      .isEqualTo(ClassName("java.lang", "String"))
+  }
+
+  @Test fun bestGuessNonAscii() {
+    val className = ClassName.bestGuess(
+      "com.\ud835\udc1andro\ud835\udc22d.\ud835\udc00ctiv\ud835\udc22ty"
+    )
+    assertEquals("com.\ud835\udc1andro\ud835\udc22d", className.packageName)
+    assertEquals("\ud835\udc00ctiv\ud835\udc22ty", className.simpleName)
+  }
+
+  internal class OuterClass {
+    internal class InnerClass
+  }
+
+  @Test fun bestGuessForString_nestedClass() {
+    assertThat(ClassName.bestGuess(Map.Entry::class.java.canonicalName))
+      .isEqualTo(ClassName("java.util", "Map", "Entry"))
+    assertThat(ClassName.bestGuess(OuterClass.InnerClass::class.java.canonicalName))
+      .isEqualTo(
+        ClassName(
+          "com.squareup.kotlinpoet",
+          "ClassNameTest", "OuterClass", "InnerClass"
+        )
+      )
+  }
+
+  @Test fun bestGuessForString_defaultPackage() {
+    assertThat(ClassName.bestGuess("SomeClass"))
+      .isEqualTo(ClassName("", "SomeClass"))
+    assertThat(ClassName.bestGuess("SomeClass.Nested"))
+      .isEqualTo(ClassName("", "SomeClass", "Nested"))
+    assertThat(ClassName.bestGuess("SomeClass.Nested.EvenMore"))
+      .isEqualTo(ClassName("", "SomeClass", "Nested", "EvenMore"))
+  }
+
+  @Test fun bestGuessForString_confusingInput() {
+    assertBestGuessThrows("")
+    assertBestGuessThrows(".")
+    assertBestGuessThrows(".Map")
+    assertBestGuessThrows("java")
+    assertBestGuessThrows("java.util")
+    assertBestGuessThrows("java.util.")
+    assertBestGuessThrows("java..util.Map.Entry")
+    assertBestGuessThrows("java.util..Map.Entry")
+    assertBestGuessThrows("kotlin.collections.Map..Entry")
+    assertBestGuessThrows("com.test.$")
+    assertBestGuessThrows("com.test.LooksLikeAClass.pkg")
+    assertBestGuessThrows("!@#\$gibberish%^&*")
+  }
+
+  private fun assertBestGuessThrows(s: String) {
+    assertThrows<IllegalArgumentException> {
+      ClassName.bestGuess(s)
+    }
+  }
+
+  @Test fun createNestedClass() {
+    val foo = ClassName("com.example", "Foo")
+    val bar = foo.nestedClass("Bar")
+    assertThat(bar).isEqualTo(ClassName("com.example", "Foo", "Bar"))
+    val baz = bar.nestedClass("Baz")
+    assertThat(baz).isEqualTo(ClassName("com.example", "Foo", "Bar", "Baz"))
+  }
+
+  @Test fun classNameFromTypeElement() {
+    val elements = compilationRule.elements
+    val element = elements.getTypeElement(Any::class.java.canonicalName)
+    assertThat(element.asClassName().toString()).isEqualTo("java.lang.Object")
+  }
+
+  @Test fun classNameFromClass() {
+    assertThat(Any::class.java.asClassName().toString())
+      .isEqualTo("java.lang.Object")
+    assertThat(OuterClass.InnerClass::class.java.asClassName().toString())
+      .isEqualTo("com.squareup.kotlinpoet.ClassNameTest.OuterClass.InnerClass")
+  }
+
+  @Test fun classNameFromKClass() {
+    assertThat(Any::class.asClassName().toString())
+      .isEqualTo("kotlin.Any")
+    assertThat(OuterClass.InnerClass::class.asClassName().toString())
+      .isEqualTo("com.squareup.kotlinpoet.ClassNameTest.OuterClass.InnerClass")
+  }
+
+  @Test fun peerClass() {
+    assertThat(java.lang.Double::class.asClassName().peerClass("Short"))
+      .isEqualTo(java.lang.Short::class.asClassName())
+    assertThat(ClassName("", "Double").peerClass("Short"))
+      .isEqualTo(ClassName("", "Short"))
+    assertThat(ClassName("a.b", "Combo", "Taco").peerClass("Burrito"))
+      .isEqualTo(ClassName("a.b", "Combo", "Burrito"))
+  }
+
+  @Test fun fromClassRejectionTypes() {
+    assertThrows<IllegalArgumentException> {
+      java.lang.Integer.TYPE.asClassName()
+    }
+
+    assertThrows<IllegalArgumentException> {
+      Void.TYPE.asClassName()
+    }
+
+    assertThrows<IllegalArgumentException> {
+      Array<Any>::class.java.asClassName()
+    }
+
+    // TODO
+    // assertThrows<IllegalArgumentException> {
+    //  Array<Int>::class.asClassName()
+    // }
+  }
+
+  @Suppress("DEPRECATION_ERROR") // Ensure still throws in case called from Java.
+  @Test fun fromEmptySimpleName() {
+    assertThrows<IllegalArgumentException> {
+      ClassName("foo" /* no simple name */)
+    }
+  }
+
+  @Test fun reflectionName() {
+    assertThat(ANY.reflectionName())
+      .isEqualTo("kotlin.Any")
+    assertThat(Thread.State::class.asClassName().reflectionName())
+      .isEqualTo("java.lang.Thread\$State")
+    assertThat(Map.Entry::class.asClassName().reflectionName())
+      .isEqualTo("kotlin.collections.Map\$Entry")
+    assertThat(ClassName("", "Foo").reflectionName())
+      .isEqualTo("Foo")
+    assertThat(ClassName("", "Foo", "Bar", "Baz").reflectionName())
+      .isEqualTo("Foo\$Bar\$Baz")
+    assertThat(ClassName("a.b.c", "Foo", "Bar", "Baz").reflectionName())
+      .isEqualTo("a.b.c.Foo\$Bar\$Baz")
+  }
+
+  @Test fun constructorReferences() {
+    assertThat(String::class.asClassName().constructorReference().toString())
+      .isEqualTo("::kotlin.String")
+    assertThat(Thread.State::class.asClassName().constructorReference().toString())
+      .isEqualTo("java.lang.Thread::State")
+    assertThat(ClassName("", "Foo").constructorReference().toString())
+      .isEqualTo("::Foo")
+    assertThat(ClassName("", "Foo", "Bar", "Baz").constructorReference().toString())
+      .isEqualTo("Foo.Bar::Baz")
+    assertThat(ClassName("a.b.c", "Foo", "Bar", "Baz").constructorReference().toString())
+      .isEqualTo("a.b.c.Foo.Bar::Baz")
+  }
+
+  @Test fun spacesEscaping() {
+    val tacoFactory = ClassName("com.squareup.taco factory", "Taco Factory")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("println(%T.produceTacos())", tacoFactory)
+          .build()
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.`taco factory`.`Taco Factory`
+      |import kotlin.Unit
+      |
+      |public fun main(): Unit {
+      |  println(`Taco Factory`.produceTacos())
+      |}
+      |""".trimMargin()
+    )
+  }
+
+  @Test fun emptySimpleNamesForbidden() {
+    assertThrows<IllegalArgumentException> {
+      ClassName(packageName = "", simpleNames = emptyArray())
+    }.hasMessageThat().isEqualTo("simpleNames must not be empty")
+
+    assertThrows<IllegalArgumentException> {
+      ClassName(packageName = "", simpleNames = arrayOf("Foo", "Bar", ""))
+    }.hasMessageThat().isEqualTo(
+      "simpleNames must not contain empty items: " +
+        "[Foo, Bar, ]"
+    )
+
+    assertThrows<IllegalArgumentException> {
+      ClassName(packageName = "", simpleNames = emptyList())
+    }.hasMessageThat().isEqualTo("simpleNames must not be empty")
+
+    assertThrows<IllegalArgumentException> {
+      ClassName(packageName = "", simpleNames = listOf("Foo", "Bar", ""))
+    }.hasMessageThat().isEqualTo(
+      "simpleNames must not contain empty items: " +
+        "[Foo, Bar, ]"
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt
new file mode 100644
index 0000000..23ef056
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import kotlin.test.Test
+
+class CodeBlockTest {
+  @Test fun equalsAndHashCode() {
+    var a = CodeBlock.builder().build()
+    var b = CodeBlock.builder().build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = CodeBlock.builder().add("%L", "taco").build()
+    b = CodeBlock.builder().add("%L", "taco").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun of() {
+    val a = CodeBlock.of("%L taco", "delicious")
+    assertThat(a.toString()).isEqualTo("delicious taco")
+  }
+
+  @Test fun percentEscapeCannotBeIndexed() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1%", "taco").build()
+    }.hasMessageThat().isEqualTo("%% may not have an index")
+  }
+
+  @Test fun nameFormatCanBeIndexed() {
+    val block = CodeBlock.builder().add("%1N", "taco").build()
+    assertThat(block.toString()).isEqualTo("taco")
+  }
+
+  @Test fun literalFormatCanBeIndexed() {
+    val block = CodeBlock.builder().add("%1L", "taco").build()
+    assertThat(block.toString()).isEqualTo("taco")
+  }
+
+  @Test fun stringFormatCanBeIndexed() {
+    val block = CodeBlock.builder().add("%1S", "taco").build()
+    assertThat(block.toString()).isEqualTo("\"taco\"")
+  }
+
+  @Test fun typeFormatCanBeIndexed() {
+    val block = CodeBlock.builder().add("%1T", String::class).build()
+    assertThat(block.toString()).isEqualTo("kotlin.String")
+  }
+
+  @Test fun simpleNamedArgument() {
+    val map = LinkedHashMap<String, Any>()
+    map["text"] = "taco"
+    val block = CodeBlock.builder().addNamed("%text:S", map).build()
+    assertThat(block.toString()).isEqualTo("\"taco\"")
+  }
+
+  @Test fun repeatedNamedArgument() {
+    val map = LinkedHashMap<String, Any>()
+    map["text"] = "tacos"
+    val block = CodeBlock.builder()
+      .addNamed("\"I like \" + %text:S + \". Do you like \" + %text:S + \"?\"", map)
+      .build()
+    assertThat(block.toString()).isEqualTo(
+      "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"",
+    )
+  }
+
+  @Test fun namedAndNoArgFormat() {
+    val map = LinkedHashMap<String, Any>()
+    map["text"] = "tacos"
+    val block = CodeBlock.builder()
+      .addNamed("⇥\n%text:L for\n⇤%%3.50", map).build()
+    assertThat(block.toString()).isEqualTo("\n  tacos for\n%3.50")
+  }
+
+  @Test fun missingNamedArgument() {
+    assertThrows<IllegalArgumentException> {
+      val map = LinkedHashMap<String, Any>()
+      CodeBlock.builder().addNamed("%text:S", map).build()
+    }.hasMessageThat().isEqualTo("Missing named argument for %text")
+  }
+
+  @Test fun lowerCaseNamed() {
+    assertThrows<IllegalArgumentException> {
+      val map = LinkedHashMap<String, Any>()
+      map["Text"] = "tacos"
+      CodeBlock.builder().addNamed("%Text:S", map).build()
+    }.hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character")
+  }
+
+  @Test fun multipleNamedArguments() {
+    val map = LinkedHashMap<String, Any>()
+    map["pipe"] = System::class
+    map["text"] = "tacos"
+
+    val block = CodeBlock.builder()
+      .addNamed("%pipe:T.out.println(\"Let's eat some %text:L\");", map)
+      .build()
+
+    assertThat(block.toString()).isEqualTo(
+      "java.lang.System.out.println(\"Let's eat some tacos\");",
+    )
+  }
+
+  @Test fun namedNewline() {
+    val map = LinkedHashMap<String, Any>()
+    map["clazz"] = java.lang.Integer::class
+    val block = CodeBlock.builder().addNamed("%clazz:T\n", map).build()
+    assertThat(block.toString()).isEqualTo("kotlin.Int\n")
+  }
+
+  @Test fun danglingNamed() {
+    val map = LinkedHashMap<String, Any>()
+    map["clazz"] = Int::class
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().addNamed("%clazz:T%", map).build()
+    }.hasMessageThat().isEqualTo("dangling % at end")
+  }
+
+  @Test fun indexTooHigh() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%2T", String::class).build()
+    }.hasMessageThat().isEqualTo("index 2 for '%2T' not in range (received 1 arguments)")
+  }
+
+  @Test fun indexIsZero() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%0T", String::class).build()
+    }.hasMessageThat().isEqualTo("index 0 for '%0T' not in range (received 1 arguments)")
+  }
+
+  @Test fun indexIsNegative() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%-1T", String::class).build()
+    }.hasMessageThat().isEqualTo("invalid format string: '%-1T'")
+  }
+
+  @Test fun indexWithoutFormatType() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1", String::class).build()
+    }.hasMessageThat().isEqualTo("dangling format characters in '%1'")
+  }
+
+  @Test fun indexWithoutFormatTypeNotAtStringEnd() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1 taco", String::class).build()
+    }.hasMessageThat().isEqualTo("invalid format string: '%1 taco'")
+  }
+
+  @Test fun indexButNoArguments() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1T").build()
+    }.hasMessageThat().isEqualTo("index 1 for '%1T' not in range (received 0 arguments)")
+  }
+
+  @Test fun formatIndicatorAlone() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%", String::class).build()
+    }.hasMessageThat().isEqualTo("dangling format characters in '%'")
+  }
+
+  @Test fun formatIndicatorWithoutIndexOrFormatType() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("% tacoString", String::class).build()
+    }.hasMessageThat().isEqualTo("invalid format string: '% tacoString'")
+  }
+
+  @Test fun sameIndexCanBeUsedWithDifferentFormats() {
+    val block = CodeBlock.builder()
+      .add("%1T.out.println(%1S)", System::class.asClassName())
+      .build()
+    assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")")
+  }
+
+  @Test fun tooManyStatementEnters() {
+    val codeBlock = CodeBlock.builder()
+      .addStatement("print(«%L»)", "1 + 1")
+      .build()
+    assertThrows<IllegalStateException> {
+      // We can't report this error until rendering type because code blocks might be composed.
+      codeBlock.toString()
+    }.hasMessageThat().isEqualTo(
+      """
+      |Can't open a new statement until the current statement is closed (opening « followed
+      |by another « without a closing »).
+      |Current code block:
+      |- Format parts: [«, print(, «, %L, », ), \n, »]
+      |- Arguments: [1 + 1]
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun statementExitWithoutStatementEnter() {
+    val codeBlock = CodeBlock.builder()
+      .addStatement("print(%L»)", "1 + 1")
+      .build()
+    assertThrows<IllegalStateException> {
+      // We can't report this error until rendering type because code blocks might be composed.
+      codeBlock.toString()
+    }.hasMessageThat().isEqualTo(
+      """
+      |Can't close a statement that hasn't been opened (closing » is not preceded by an
+      |opening «).
+      |Current code block:
+      |- Format parts: [«, print(, %L, », ), \n, »]
+      |- Arguments: [1 + 1]
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nullableType() {
+    val type = String::class.asTypeName().copy(nullable = true)
+    val typeBlock = CodeBlock.of("%T", type)
+    assertThat(typeBlock.toString()).isEqualTo("kotlin.String?")
+
+    val list = (List::class.asClassName().copy(nullable = true) as ClassName)
+      .parameterizedBy(Int::class.asTypeName().copy(nullable = true))
+      .copy(nullable = true)
+    val listBlock = CodeBlock.of("%T", list)
+    assertThat(listBlock.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
+
+    val map = (Map::class.asClassName().copy(nullable = true) as ClassName)
+      .parameterizedBy(String::class.asTypeName().copy(nullable = true), list)
+      .copy(nullable = true)
+    val mapBlock = CodeBlock.of("%T", map)
+    assertThat(mapBlock.toString())
+      .isEqualTo("kotlin.collections.Map<kotlin.String?, kotlin.collections.List<kotlin.Int?>?>?")
+
+    val rarr = WildcardTypeName.producerOf(String::class.asTypeName().copy(nullable = true))
+    val rarrBlock = CodeBlock.of("%T", rarr)
+    assertThat(rarrBlock.toString()).isEqualTo("out kotlin.String?")
+  }
+
+  @Test fun withoutPrefixMatching() {
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("")),
+    )
+      .isEqualTo(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("ab")),
+    )
+      .isEqualTo(CodeBlock.of("cd %S efgh %S ijkl", "x", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd ")),
+    )
+      .isEqualTo(CodeBlock.of("%S efgh %S ijkl", "x", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S", "x")),
+    )
+      .isEqualTo(CodeBlock.of(" efgh %S ijkl", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S ef", "x")),
+    )
+      .isEqualTo(CodeBlock.of("gh %S ijkl", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh ", "x")),
+    )
+      .isEqualTo(CodeBlock.of("%S ijkl", "y"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S", "x", "y")),
+    )
+      .isEqualTo(CodeBlock.of(" ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "y")),
+    )
+      .isEqualTo(CodeBlock.of("kl"))
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+    )
+      .isEqualTo(CodeBlock.of(""))
+  }
+
+  @Test fun withoutPrefixNoArgs() {
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("")),
+    )
+      .isEqualTo(CodeBlock.of("abcd %% efgh %% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("ab")),
+    )
+      .isEqualTo(CodeBlock.of("cd %% efgh %% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd ")),
+    )
+      .isEqualTo(CodeBlock.of("%% efgh %% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %%")),
+    )
+      .isEqualTo(CodeBlock.of(" efgh %% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %% ef")),
+    )
+      .isEqualTo(CodeBlock.of("gh %% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %% efgh ")),
+    )
+      .isEqualTo(CodeBlock.of("%% ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %% efgh %%")),
+    )
+      .isEqualTo(CodeBlock.of(" ijkl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %% efgh %% ij")),
+    )
+      .isEqualTo(CodeBlock.of("kl"))
+    assertThat(
+      CodeBlock.of("abcd %% efgh %% ijkl")
+        .withoutPrefix(CodeBlock.of("abcd %% efgh %% ijkl")),
+    )
+      .isEqualTo(CodeBlock.of(""))
+  }
+
+  @Test fun withoutPrefixArgMismatch() {
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "z")),
+    )
+      .isNull()
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "z", "y")),
+    )
+      .isNull()
+  }
+
+  @Test fun withoutPrefixFormatPartMismatch() {
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgx %S ij", "x", "y")),
+    )
+      .isNull()
+    assertThat(
+      CodeBlock.of("abcd %S efgh %% ijkl", "x")
+        .withoutPrefix(CodeBlock.of("abcd %% efgh %S ij", "x")),
+    )
+      .isNull()
+  }
+
+  @Test fun withoutPrefixTooShort() {
+    assertThat(
+      CodeBlock.of("abcd %S efgh %S", "x", "y")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+    )
+      .isNull()
+    assertThat(
+      CodeBlock.of("abcd %S efgh", "x")
+        .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+    )
+      .isNull()
+  }
+
+  @Test fun trimEmpty() {
+    assertThat(CodeBlock.of("").trim())
+      .isEqualTo(CodeBlock.of(""))
+  }
+
+  @Test fun trimNoPlaceholders() {
+    assertThat(CodeBlock.of("return null").trim())
+      .isEqualTo(CodeBlock.of("return null"))
+  }
+
+  @Test fun trimPlaceholdersWithArgs() {
+    assertThat(CodeBlock.of("return %S", "taco").trim())
+      .isEqualTo(CodeBlock.of("return %S", "taco"))
+  }
+
+  @Test fun trimNoArgPlaceholderMiddle() {
+    assertThat(CodeBlock.of("this.taco = %S", "taco").trim())
+      .isEqualTo(CodeBlock.of("this.taco = %S", "taco"))
+  }
+
+  @Test fun trimNoArgPlaceholderStart() {
+    assertThat(CodeBlock.of("⇥return ").trim())
+      .isEqualTo(CodeBlock.of("return "))
+  }
+
+  @Test fun trimNoArgPlaceholderEnd() {
+    assertThat(CodeBlock.of("return ⇥").trim())
+      .isEqualTo(CodeBlock.of("return "))
+  }
+
+  @Test fun trimNoArgPlaceholdersStartEnd() {
+    assertThat(CodeBlock.of("«return this»").trim())
+      .isEqualTo(CodeBlock.of("return this"))
+  }
+
+  @Test fun trimMultipleNoArgPlaceholders() {
+    assertThat(
+      CodeBlock.of("«return if (x > %L) %S else %S»", 1, "a", "b").trim(),
+    )
+      .isEqualTo(CodeBlock.of("return if (x > %L) %S else %S", 1, "a", "b"))
+  }
+
+  @Test fun trimOnlyNoArgPlaceholders() {
+    assertThat(CodeBlock.of("«»⇥⇤").trim())
+      .isEqualTo(CodeBlock.of(""))
+  }
+
+  @Test fun replaceSimple() {
+    assertThat(CodeBlock.of("%%⇥%%").replaceAll("%%", ""))
+      .isEqualTo(CodeBlock.of("⇥"))
+  }
+
+  @Test fun replaceNoMatches() {
+    assertThat(CodeBlock.of("%%⇥%%").replaceAll("⇤", ""))
+      .isEqualTo(CodeBlock.of("%%⇥%%"))
+  }
+
+  @Test fun replaceRegex() {
+    assertThat(CodeBlock.of("%%⇥%%⇤").replaceAll("[⇥|⇤]", ""))
+      .isEqualTo(CodeBlock.of("%%%%"))
+  }
+
+  @Test fun joinToCode() {
+    val blocks = listOf(CodeBlock.of("%L", "taco1"), CodeBlock.of("%L", "taco2"), CodeBlock.of("%L", "taco3"))
+    assertThat(blocks.joinToCode(prefix = "(", suffix = ")"))
+      .isEqualTo(CodeBlock.of("(%L, %L, %L)", "taco1", "taco2", "taco3"))
+  }
+
+  @Test fun beginControlFlowWithParams() {
+    val controlFlow = CodeBlock.builder()
+      .beginControlFlow("list.forEach { element ->")
+      .addStatement("println(element)")
+      .endControlFlow()
+      .build()
+    assertThat(controlFlow.toString()).isEqualTo(
+      """
+      |list.forEach { element ->
+      |  println(element)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun beginControlFlowWithParamsAndTemplateString() {
+    val controlFlow = CodeBlock.builder()
+      .beginControlFlow("listOf(\"\${1.toString()}\").forEach { element ->")
+      .addStatement("println(element)")
+      .endControlFlow()
+      .build()
+    assertThat(controlFlow.toString()).isEqualTo(
+      """
+      |listOf("${'$'}{1.toString()}").forEach { element ->
+      |  println(element)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun buildCodeBlock() {
+    val codeBlock = buildCodeBlock {
+      beginControlFlow("if (2 == 2)")
+      addStatement("println(%S)", "foo")
+      nextControlFlow("else")
+      addStatement("println(%S)", "bar")
+      endControlFlow()
+    }
+    assertThat(codeBlock.toString()).isEqualTo(
+      """
+      |if (2 == 2) {
+      |  println("foo")
+      |} else {
+      |  println("bar")
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nonWrappingControlFlow() {
+    val file = FileSpec.builder("com.squareup.tacos", "Test")
+      .addFunction(
+        FunSpec.builder("test")
+          .beginControlFlow("if (%1S == %1S)", "Very long string that would wrap the line ")
+          .nextControlFlow("else if (%1S == %1S)", "Long string that would wrap the line 2 ")
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun test(): Unit {
+      |  if ("Very long string that would wrap the line " ==
+      |      "Very long string that would wrap the line ") {
+      |  } else if ("Long string that would wrap the line 2 " ==
+      |      "Long string that would wrap the line 2 ") {
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun ensureEndsWithNewLineWithNoArgs() {
+    val codeBlock = CodeBlock.builder()
+      .addStatement("Modeling a kdoc")
+      .add("\n")
+      .addStatement("Statement with no args")
+      .build()
+
+    assertThat(codeBlock.ensureEndsWithNewLine().toString()).isEqualTo(
+      """
+      |Modeling a kdoc
+      |
+      |Statement with no args
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun `%N escapes keywords`() {
+    val funSpec = FunSpec.builder("object").build()
+    assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`object`")
+  }
+
+  @Test fun `%N escapes spaces`() {
+    val funSpec = FunSpec.builder("create taco").build()
+    assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`create taco`")
+  }
+
+  @Test fun clear() {
+    val blockBuilder = CodeBlock.builder().addStatement("%S is some existing code", "This")
+
+    blockBuilder.clear()
+
+    assertThat(blockBuilder.build().toString()).isEmpty()
+  }
+
+  @Test fun withIndent() {
+    val codeBlock = CodeBlock.Builder()
+      .apply {
+        addStatement("User(")
+        withIndent {
+          addStatement("age = 42,")
+          addStatement("cities = listOf(")
+          withIndent {
+            addStatement("%S,", "Berlin")
+            addStatement("%S,", "London")
+          }
+          addStatement(")")
+        }
+        addStatement(")")
+      }
+      .build()
+
+    assertThat(codeBlock.toString()).isEqualTo(
+      """
+      |User(
+      |  age = 42,
+      |  cities = listOf(
+      |    "Berlin",
+      |    "London",
+      |  )
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1236
+  @Test fun dontEscapeBackslashesInRawStrings() {
+    // println("ESCAPE '\\'") -> ESCAPE '\'
+    assertThat(CodeBlock.of("%S", "ESCAPE '\\'").toString()).isEqualTo("\"ESCAPE '\\\\'\"")
+    // println("""ESCAPE '\'""") -> ESCAPE '\'
+    assertThat(CodeBlock.of("%P", """ESCAPE '\'""").toString()).isEqualTo("\"\"\"ESCAPE '\\'\"\"\"")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1381
+  @Test fun useUnderscoresOnLargeDecimalLiterals() {
+    assertThat(CodeBlock.of("%L", 10000).toString()).isEqualTo("10_000")
+    assertThat(CodeBlock.of("%L", 100000L).toString()).isEqualTo("100_000")
+    assertThat(CodeBlock.of("%L", Int.MIN_VALUE).toString()).isEqualTo("-2_147_483_648")
+    assertThat(CodeBlock.of("%L", Int.MAX_VALUE).toString()).isEqualTo("2_147_483_647")
+    assertThat(CodeBlock.of("%L", Long.MIN_VALUE).toString()).isEqualTo("-9_223_372_036_854_775_808")
+    assertThat(CodeBlock.of("%L", 10000.123).toString()).isEqualTo("10_000.123")
+    assertThat(CodeBlock.of("%L", 3.0).toString()).isEqualTo("3.0")
+    assertThat(CodeBlock.of("%L", 10000.123f).toString()).isEqualTo("10_000.123")
+    assertThat(CodeBlock.of("%L", 10000.123456789012).toString()).isEqualTo("10_000.123456789011")
+    assertThat(CodeBlock.of("%L", 1281.toShort()).toString()).isEqualTo("1_281")
+
+    assertThat(CodeBlock.of("%S", 10000).toString()).isEqualTo("\"10000\"")
+    assertThat(CodeBlock.of("%S", 100000L).toString()).isEqualTo("\"100000\"")
+    assertThat(CodeBlock.of("%S", Int.MIN_VALUE).toString()).isEqualTo("\"-2147483648\"")
+    assertThat(CodeBlock.of("%S", Int.MAX_VALUE).toString()).isEqualTo("\"2147483647\"")
+    assertThat(CodeBlock.of("%S", Long.MIN_VALUE).toString()).isEqualTo("\"-9223372036854775808\"")
+    assertThat(CodeBlock.of("%S", 10000.123).toString()).isEqualTo("\"10000.123\"")
+    assertThat(CodeBlock.of("%S", 3.0).toString()).isEqualTo("\"3.0\"")
+    assertThat(CodeBlock.of("%S", 10000.123f).toString()).isEqualTo("\"10000.123\"")
+    assertThat(CodeBlock.of("%S", 10000.12345678901).toString()).isEqualTo("\"10000.12345678901\"")
+    assertThat(CodeBlock.of("%S", 1281.toShort()).toString()).isEqualTo("\"1281\"")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt
new file mode 100644
index 0000000..5074b91
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.test.Test
+
+class CrossplatformTest {
+
+  @Test fun crossplatform() {
+    val expectTypeParam = TypeVariableName("V")
+    val expectType = "AtomicRef"
+    val expectSpec = TypeSpec.expectClassBuilder(expectType)
+      .addTypeVariable(expectTypeParam)
+      .addModifiers(KModifier.INTERNAL)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("value", expectTypeParam)
+          .build(),
+      )
+      .addProperty(PropertySpec.builder("value", expectTypeParam).build())
+      .addFunction(
+        FunSpec.builder("get")
+          .returns(expectTypeParam)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("set")
+          .addParameter("value", expectTypeParam)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("getAndSet")
+          .addParameter("value", expectTypeParam)
+          .returns(expectTypeParam)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("compareAndSet")
+          .addParameter("expect", expectTypeParam)
+          .addParameter("update", expectTypeParam)
+          .returns(Boolean::class)
+          .build(),
+      )
+      .build()
+    val actualName = AtomicReference::class.asTypeName().parameterizedBy(expectTypeParam)
+    val actualSpec = TypeAliasSpec.builder(expectType, actualName)
+      .addTypeVariable(expectTypeParam)
+      .addModifiers(KModifier.ACTUAL)
+      .build()
+    val fileSpec = FileSpec.builder("", "Test")
+      .addType(expectSpec)
+      .addTypeAlias(actualSpec)
+      .build()
+
+    assertThat(fileSpec.toString()).isEqualTo(
+      """
+      |import java.util.concurrent.atomic.AtomicReference
+      |import kotlin.Boolean
+      |import kotlin.Unit
+      |
+      |internal expect class AtomicRef<V>(
+      |  `value`: V,
+      |) {
+      |  public val `value`: V
+      |
+      |  public fun `get`(): V
+      |
+      |  public fun `set`(`value`: V): Unit
+      |
+      |  public fun getAndSet(`value`: V): V
+      |
+      |  public fun compareAndSet(`expect`: V, update: V): Boolean
+      |}
+      |
+      |public actual typealias AtomicRef<V> = AtomicReference<V>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun expectWithSecondaryConstructors() {
+    val expectSpec = TypeSpec.expectClassBuilder("IoException")
+      .addModifiers(KModifier.OPEN)
+      .superclass(Exception::class)
+      .addFunction(FunSpec.constructorBuilder().build())
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addParameter("message", String::class)
+          .build(),
+      )
+      .build()
+    val fileSpec = FileSpec.builder("", "Test")
+      .addType(expectSpec)
+      .build()
+
+    assertThat(fileSpec.toString()).isEqualTo(
+      """
+      |import java.lang.Exception
+      |import kotlin.String
+      |
+      |public expect open class IoException : Exception {
+      |  public constructor()
+      |
+      |  public constructor(message: String)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun topLevelProperties() {
+    val fileSpec = FileSpec.builder("", "Test")
+      .addProperty(PropertySpec.builder("bar", String::class, KModifier.EXPECT).build())
+      .addProperty(
+        PropertySpec.builder("bar", String::class, KModifier.ACTUAL)
+          .initializer(CodeBlock.of("%S", "Hello"))
+          .build(),
+      )
+      .build()
+
+    assertThat(fileSpec.toString()).isEqualTo(
+      """
+      |import kotlin.String
+      |
+      |public expect val bar: String
+      |
+      |public actual val bar: String = "Hello"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun topLevelFunctions() {
+    val fileSpec = FileSpec.builder("", "Test")
+      .addFunction(
+        FunSpec.builder("f1")
+          .addModifiers(KModifier.EXPECT)
+          .returns(Int::class)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("f1")
+          .addModifiers(KModifier.ACTUAL)
+          .addStatement("return 1")
+          .build(),
+      )
+      .build()
+
+    assertThat(fileSpec.toString()).isEqualTo(
+      """
+      |import kotlin.Int
+      |
+      |public expect fun f1(): Int
+      |
+      |public actual fun f1() = 1
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun initBlockInExpectForbidden() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.expectClassBuilder("AtomicRef")
+        .addInitializerBlock(CodeBlock.of("println()"))
+    }.hasMessageThat().isEqualTo("expect CLASS can't have initializer blocks")
+  }
+
+  @Test fun expectFunctionBodyForbidden() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.expectClassBuilder("AtomicRef")
+        .addFunction(
+          FunSpec.builder("print")
+            .addStatement("println()")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("functions in expect classes can't have bodies")
+  }
+
+  @Test fun expectPropertyInitializerForbidden() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.expectClassBuilder("AtomicRef")
+        .addProperty(
+          PropertySpec.builder("a", Boolean::class)
+            .initializer("true")
+            .build(),
+        )
+    }.hasMessageThat().isEqualTo("properties in expect classes can't have initializers")
+  }
+
+  @Test fun expectPropertyGetterForbidden() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.expectClassBuilder("AtomicRef")
+        .addProperty(
+          PropertySpec.builder("a", Boolean::class)
+            .getter(
+              FunSpec.getterBuilder()
+                .addStatement("return true")
+                .build(),
+            )
+            .build(),
+        )
+    }.hasMessageThat().isEqualTo("properties in expect classes can't have getters and setters")
+  }
+
+  @Test fun expectPropertySetterForbidden() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.expectClassBuilder("AtomicRef")
+        .addProperty(
+          PropertySpec.builder("a", Boolean::class)
+            .mutable()
+            .setter(
+              FunSpec.setterBuilder()
+                .addParameter("value", Boolean::class)
+                .addStatement("field = true")
+                .build(),
+            )
+            .build(),
+        )
+    }.hasMessageThat().isEqualTo("properties in expect classes can't have getters and setters")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt
new file mode 100644
index 0000000..80a9035
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class DelegatedConstructorCallTest {
+  @Test
+  fun defaultPresentInClass() {
+    val builder = TypeSpec.classBuilder("Test")
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public class Test : testpackage.TestSuper()
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun defaultPresentInObject() {
+    val builder = TypeSpec.objectBuilder("Test")
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public object Test : testpackage.TestSuper()
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun defaultNotPresentInExternalClass() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test : testpackage.TestSuper
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun defaultNotPresentInExpectClass() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXPECT)
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public expect class Test : testpackage.TestSuper
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun defaultNotPresentInExpectObject() {
+    val builder = TypeSpec.objectBuilder("Test")
+      .addModifiers(KModifier.EXPECT)
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public expect object Test : testpackage.TestSuper
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun defaultNotPresentInExternalObject() {
+    val builder = TypeSpec.objectBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+      .superclass(ClassName("testpackage", "TestSuper"))
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external object Test : testpackage.TestSuper
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun allowedInClass() {
+    val builder = TypeSpec.classBuilder("Test")
+      .superclass(ClassName("testpackage", "TestSuper"))
+      .addSuperclassConstructorParameter("anything")
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public class Test : testpackage.TestSuper(anything)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun allowedInObject() {
+    val builder = TypeSpec.objectBuilder("Test")
+      .superclass(ClassName("testpackage", "TestSuper"))
+      .addSuperclassConstructorParameter("anything")
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public object Test : testpackage.TestSuper(anything)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun allowedInClassSecondary() {
+    val builder = TypeSpec.classBuilder("Test")
+    val primaryConstructorBuilder = FunSpec.constructorBuilder()
+    val primaryConstructor = primaryConstructorBuilder.build()
+    builder.primaryConstructor(primaryConstructor)
+    val secondaryConstructorBuilder = FunSpec.constructorBuilder()
+      .addParameter(ParameterSpec("foo", ClassName("kotlin", "String")))
+      .callThisConstructor()
+    builder.addFunction(secondaryConstructorBuilder.build())
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public class Test() {
+        |  public constructor(foo: kotlin.String) : this()
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun notAllowedInExternalClass() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+      .superclass(ClassName("testpackage", "TestSuper"))
+      .addSuperclassConstructorParameter("anything")
+    assertThrows<IllegalStateException> {
+      builder.build()
+    }
+  }
+
+  @Test
+  fun notAllowedInExternalObject() {
+    val builder = TypeSpec.objectBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+      .superclass(ClassName("testpackage", "TestSuper"))
+      .addSuperclassConstructorParameter("anything")
+    assertThrows<IllegalStateException> {
+      builder.build()
+    }
+  }
+
+  @Test
+  fun notAllowedInExternalClassSecondary() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val primaryConstructorBuilder = FunSpec.constructorBuilder()
+    builder.primaryConstructor(primaryConstructorBuilder.build())
+    val secondaryConstructorBuilder = FunSpec.constructorBuilder()
+      .addParameter(ParameterSpec("foo", ClassName("kotlin", "String")))
+      .callThisConstructor()
+    builder.addFunction(secondaryConstructorBuilder.build())
+    assertThrows<IllegalStateException> {
+      builder.build()
+    }
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt
new file mode 100644
index 0000000..5cb7753
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class ExpectDeclarationsTest {
+  @Test fun expectFunDeclaration() {
+    val methodSpec = FunSpec.builder("function")
+      .addModifiers(KModifier.EXPECT)
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public expect fun function(): kotlin.Unit
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExpectFunDeclaration() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXPECT)
+    val methodSpec = FunSpec.builder("function")
+      .build()
+    builder.addFunction(methodSpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public expect class Test {
+        |  public fun function(): kotlin.Unit
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun expectPropertyDeclaration() {
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .addModifiers(KModifier.EXPECT)
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |expect val prop: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExpectPropertyDeclaration() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXPECT)
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .build()
+    builder.addProperty(propertySpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public expect class Test {
+        |  public val prop: kotlin.String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt
new file mode 100644
index 0000000..42719c4
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class ExternalDeclarationsTest {
+  @Test fun externalFunDeclarationWithoutBody() {
+    val methodSpec = FunSpec.builder("function")
+      .addModifiers(KModifier.EXTERNAL)
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public external fun function(): kotlin.Unit
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalFunDeclarationWithDefinedExternally() {
+    val methodSpec = FunSpec.builder("function")
+      .addModifiers(KModifier.EXTERNAL)
+      .addCode("return kotlin.js.definedExternally")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public external fun function() = kotlin.js.definedExternally
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalFunDeclarationWithDefinedExternally2() {
+    val methodSpec = FunSpec.builder("function")
+      .addModifiers(KModifier.EXTERNAL)
+      .addCode("kotlin.js.definedExternally")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public external fun function(): kotlin.Unit {
+      |  kotlin.js.definedExternally
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExternalFunDeclarationWithoutBody() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val methodSpec = FunSpec.builder("function")
+      .build()
+    builder.addFunction(methodSpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test {
+        |  public fun function(): kotlin.Unit
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExternalFunDeclarationWithDefinedExternally() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val methodSpec = FunSpec.builder("function")
+      .addCode("return kotlin.js.definedExternally")
+      .build()
+    builder.addFunction(methodSpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test {
+        |  public fun function() = kotlin.js.definedExternally
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExternalFunDeclarationWithDefinedExternally2() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val methodSpec = FunSpec.builder("function")
+      .addModifiers(KModifier.EXTERNAL)
+      .addCode("kotlin.js.definedExternally")
+      .build()
+    builder.addFunction(methodSpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test {
+        |  public fun function(): kotlin.Unit {
+        |    kotlin.js.definedExternally
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalPropertyDeclarationWithoutInitializer() {
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .addModifiers(KModifier.EXTERNAL)
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |external val prop: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalPropertyDeclarationWithDefinedExternally() {
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .addModifiers(KModifier.EXTERNAL)
+      .initializer("kotlin.js.definedExternally")
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |external val prop: kotlin.String = kotlin.js.definedExternally
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExternalPropertyDeclarationWithoutInitializer() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .build()
+    builder.addProperty(propertySpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test {
+        |  public val prop: kotlin.String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implicitExternalPropertyDeclarationWithDefinedExternally() {
+    val builder = TypeSpec.classBuilder("Test")
+      .addModifiers(KModifier.EXTERNAL)
+    val propertySpec = PropertySpec.builder("prop", String::class)
+      .initializer("kotlin.js.definedExternally")
+      .build()
+    builder.addProperty(propertySpec)
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+        |public external class Test {
+        |  public val prop: kotlin.String = kotlin.js.definedExternally
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt
new file mode 100644
index 0000000..35719cf
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.io.ByteStreams
+import com.google.common.truth.Truth.assertThat
+import java.net.URI
+import java.nio.charset.StandardCharsets.UTF_8
+import javax.tools.JavaFileObject.Kind
+import kotlin.test.Test
+
+class FileReadingTest {
+  @Test fun javaFileObjectUri() {
+    val type = TypeSpec.classBuilder("Test").build()
+    assertThat(FileSpec.get("", type).toJavaFileObject().toUri())
+      .isEqualTo(URI.create("Test.kt"))
+    assertThat(FileSpec.get("foo", type).toJavaFileObject().toUri())
+      .isEqualTo(URI.create("foo/Test.kt"))
+    assertThat(FileSpec.get("com.example", type).toJavaFileObject().toUri())
+      .isEqualTo(URI.create("com/example/Test.kt"))
+  }
+
+  @Test fun javaFileObjectKind() {
+    val source = FileSpec.get("", TypeSpec.classBuilder("Test").build())
+    assertThat(source.toJavaFileObject().kind).isEqualTo(Kind.SOURCE)
+  }
+
+  @Test fun javaFileObjectCharacterContent() {
+    val type = TypeSpec.classBuilder("Test")
+      .addKdoc("Pi\u00f1ata\u00a1")
+      .addFunction(FunSpec.builder("fooBar").build())
+      .build()
+    val source = FileSpec.get("foo", type)
+    val javaFileObject = source.toJavaFileObject()
+
+    // We can never have encoding issues (everything is in process)
+    assertThat(javaFileObject.getCharContent(true)).isEqualTo(source.toString())
+    assertThat(javaFileObject.getCharContent(false)).isEqualTo(source.toString())
+  }
+
+  @Test fun javaFileObjectInputStreamIsUtf8() {
+    val source = FileSpec.builder("foo", "Test")
+      .addType(TypeSpec.classBuilder("Test").build())
+      .addFileComment("Pi\u00f1ata\u00a1")
+      .build()
+    val bytes = ByteStreams.toByteArray(source.toJavaFileObject().openInputStream())
+
+    // KotlinPoet always uses UTF-8.
+    assertThat(bytes).isEqualTo(source.toString().toByteArray(UTF_8))
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt
new file mode 100644
index 0000000..03b3cfc
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.Collections
+import java.util.Date
+import java.util.concurrent.Callable
+import java.util.concurrent.TimeUnit
+import java.util.function.Function
+import kotlin.test.Ignore
+import kotlin.test.Test
+
+class FileSpecTest {
+  @Test fun importStaticReadmeExample() {
+    val hoverboard = ClassName("com.mattel", "Hoverboard")
+    val namedBoards = ClassName("com.mattel", "Hoverboard", "Boards")
+    val list = List::class.asClassName()
+    val arrayList = ClassName("java.util", "ArrayList").parameterizedBy(hoverboard)
+    val listOfHoverboards = list.parameterizedBy(hoverboard)
+    val beyond = FunSpec.builder("beyond")
+      .returns(listOfHoverboards)
+      .addStatement("val result = %T()", arrayList)
+      .addStatement("result.add(%T.createNimbus(2000))", hoverboard)
+      .addStatement("result.add(%T.createNimbus(\"2001\"))", hoverboard)
+      .addStatement("result.add(%T.createNimbus(%T.THUNDERBOLT))", hoverboard, namedBoards)
+      .addStatement("%T.sort(result)", Collections::class)
+      .addStatement("return if (result.isEmpty()) %T.emptyList() else result", Collections::class)
+      .build()
+    val hello = TypeSpec.classBuilder("HelloWorld")
+      .addFunction(beyond)
+      .build()
+    val source = FileSpec.builder("com.example.helloworld", "HelloWorld")
+      .addType(hello)
+      .addImport(hoverboard, "createNimbus")
+      .addImport(namedBoards, "THUNDERBOLT")
+      .addImport(Collections::class, "sort", "emptyList")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.example.helloworld
+        |
+        |import com.mattel.Hoverboard
+        |import com.mattel.Hoverboard.Boards.THUNDERBOLT
+        |import com.mattel.Hoverboard.createNimbus
+        |import java.util.ArrayList
+        |import java.util.Collections.emptyList
+        |import java.util.Collections.sort
+        |import kotlin.collections.List
+        |
+        |public class HelloWorld {
+        |  public fun beyond(): List<Hoverboard> {
+        |    val result = ArrayList<Hoverboard>()
+        |    result.add(createNimbus(2000))
+        |    result.add(createNimbus("2001"))
+        |    result.add(createNimbus(THUNDERBOLT))
+        |    sort(result)
+        |    return if (result.isEmpty()) emptyList() else result
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importStaticMixed() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addInitializerBlock(
+            CodeBlock.builder()
+              .addStatement("assert %1T.valueOf(\"BLOCKED\") == %1T.BLOCKED", Thread.State::class)
+              .addStatement("%T.gc()", System::class)
+              .addStatement("%1T.out.println(%1T.nanoTime())", System::class)
+              .build(),
+          )
+          .addFunction(
+            FunSpec.constructorBuilder()
+              .addParameter("states", Thread.State::class.asClassName(), VARARG)
+              .build(),
+          )
+          .build(),
+      )
+      .addImport(Thread.State.BLOCKED)
+      .addImport(System::class, "gc", "out", "nanoTime")
+      .addImport(Thread.State::class, "valueOf")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.System.`out`
+        |import java.lang.System.gc
+        |import java.lang.System.nanoTime
+        |import java.lang.Thread
+        |import java.lang.Thread.State.BLOCKED
+        |import java.lang.Thread.State.valueOf
+        |
+        |public class Taco {
+        |  init {
+        |    assert valueOf("BLOCKED") == BLOCKED
+        |    gc()
+        |    out.println(nanoTime())
+        |  }
+        |
+        |  public constructor(vararg states: Thread.State)
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importTopLevel() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addImport("com.squareup.tacos.internal", "INGREDIENTS", "wrap")
+      .addFunction(
+        FunSpec.builder("prepareTacos")
+          .returns(
+            List::class.asClassName()
+              .parameterizedBy(ClassName("com.squareup.tacos", "Taco")),
+          )
+          .addCode("return wrap(INGREDIENTS)\n")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.tacos.`internal`.INGREDIENTS
+        |import com.squareup.tacos.`internal`.wrap
+        |import kotlin.collections.List
+        |
+        |public fun prepareTacos(): List<Taco> = wrap(INGREDIENTS)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Ignore("addImport doesn't support members with %L")
+  @Test
+  fun importStaticDynamic() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addFunction(
+            FunSpec.builder("main")
+              .addStatement("%T.%L.println(%S)", System::class, "out", "hello")
+              .build(),
+          )
+          .build(),
+      )
+      .addImport(System::class, "out")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos;
+        |
+        |import static java.lang.System.out;
+        |
+        |class Taco {
+        |  void main() {
+        |    out.println("hello");
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importStaticNone() {
+    val source = FileSpec.builder("readme", "Util")
+      .addType(importStaticTypeSpec("Util"))
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package readme
+        |
+        |import java.lang.System
+        |import java.util.concurrent.TimeUnit
+        |import kotlin.Long
+        |
+        |public class Util {
+        |  public fun minutesToSeconds(minutes: Long): Long {
+        |    System.gc()
+        |    return TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES)
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importStaticOnce() {
+    val source = FileSpec.builder("readme", "Util")
+      .addType(importStaticTypeSpec("Util"))
+      .addImport(TimeUnit.SECONDS).build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package readme
+        |
+        |import java.lang.System
+        |import java.util.concurrent.TimeUnit
+        |import java.util.concurrent.TimeUnit.SECONDS
+        |import kotlin.Long
+        |
+        |public class Util {
+        |  public fun minutesToSeconds(minutes: Long): Long {
+        |    System.gc()
+        |    return SECONDS.convert(minutes, TimeUnit.MINUTES)
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importStaticTwice() {
+    val source = FileSpec.builder("readme", "Util")
+      .addType(importStaticTypeSpec("Util"))
+      .addImport(TimeUnit.SECONDS)
+      .addImport(TimeUnit.MINUTES)
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package readme
+        |
+        |import java.lang.System
+        |import java.util.concurrent.TimeUnit.MINUTES
+        |import java.util.concurrent.TimeUnit.SECONDS
+        |import kotlin.Long
+        |
+        |public class Util {
+        |  public fun minutesToSeconds(minutes: Long): Long {
+        |    System.gc()
+        |    return SECONDS.convert(minutes, MINUTES)
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importStaticWildcardsForbidden() {
+    assertThrows<IllegalArgumentException> {
+      FileSpec.builder("readme", "Util")
+        .addType(importStaticTypeSpec("Util"))
+        .addImport(TimeUnit::class, "*")
+    }.hasMessageThat().isEqualTo("Wildcard imports are not allowed")
+  }
+
+  private fun importStaticTypeSpec(name: String): TypeSpec {
+    val funSpec = FunSpec.builder("minutesToSeconds")
+      .addModifiers(KModifier.PUBLIC)
+      .returns(Long::class)
+      .addParameter("minutes", Long::class)
+      .addStatement("%T.gc()", System::class)
+      .addStatement("return %1T.SECONDS.convert(minutes, %1T.MINUTES)", TimeUnit::class)
+      .build()
+    return TypeSpec.classBuilder(name).addFunction(funSpec).build()
+  }
+
+  @Test fun noImports() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(TypeSpec.classBuilder("Taco").build())
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun singleImport() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty("madeFreshDate", Date::class)
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.util.Date
+        |
+        |public class Taco {
+        |  public val madeFreshDate: Date
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun singleImportEscapeKeywords() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty("madeFreshDate", ClassName("com.squareup.is.fun.in", "Date"))
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.`is`.`fun`.`in`.Date
+        |
+        |public class Taco {
+        |  public val madeFreshDate: Date
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeSpacesInPackageName() {
+    val file = FileSpec.builder("com.squareup.taco factory", "TacoFactory")
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.`taco factory`
+      |
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun conflictingImports() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty("madeFreshDate", Date::class)
+          .addProperty("madeFreshDatabaseDate", ClassName("java.sql", "Date"))
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.sql.Date as SqlDate
+        |import java.util.Date as UtilDate
+        |
+        |public class Taco {
+        |  public val madeFreshDate: UtilDate
+        |
+        |  public val madeFreshDatabaseDate: SqlDate
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun conflictingImportsEscapeKeywords() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty("madeFreshDate1", ClassName("com.squareup.is.fun.in", "Date"))
+          .addProperty("madeFreshDate2", ClassName("com.squareup.do.val.var", "Date"))
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.`do`.`val`.`var`.Date as VarDate
+        |import com.squareup.`is`.`fun`.`in`.Date as InDate
+        |
+        |public class Taco {
+        |  public val madeFreshDate1: InDate
+        |
+        |  public val madeFreshDate2: VarDate
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeSpacesInImports() {
+    val tacoFactory = ClassName("com.squareup.taco factory", "TacoFactory")
+    val file = FileSpec.builder("com.example", "TacoFactoryDemo")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("println(%T.produceTacos())", tacoFactory)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.`taco factory`.TacoFactory
+      |import kotlin.Unit
+      |
+      |public fun main(): Unit {
+      |  println(TacoFactory.produceTacos())
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeSpacesInAliasedImports() {
+    val tacoFactory = ClassName("com.squareup.taco factory", "TacoFactory")
+    val file = FileSpec.builder("com.example", "TacoFactoryDemo")
+      .addAliasedImport(tacoFactory, "La Taqueria")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("println(%T.produceTacos())", tacoFactory)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.`taco factory`.TacoFactory as `La Taqueria`
+      |
+      |public fun main(): Unit {
+      |  println(`La Taqueria`.produceTacos())
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun aliasedImports() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addAliasedImport(java.lang.String::class.java, "JString")
+      .addAliasedImport(String::class, "KString")
+      .addProperty(
+        PropertySpec.builder("a", java.lang.String::class.java)
+          .initializer("%T(%S)", java.lang.String::class.java, "a")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("b", String::class)
+          .initializer("%S", "b")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.lang.String as JString
+      |import kotlin.String as KString
+      |
+      |public val a: JString = JString("a")
+      |
+      |public val b: KString = "b"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumAliasedImport() {
+    val minsAlias = "MINS"
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addAliasedImport(TimeUnit::class.asClassName(), "MINUTES", minsAlias)
+      .addFunction(
+        FunSpec.builder("sleepForFiveMins")
+          .addStatement("%T.MINUTES.sleep(5)", TimeUnit::class)
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import java.util.concurrent.TimeUnit.MINUTES as MINS
+      |
+      |public fun sleepForFiveMins(): Unit {
+      |  MINS.sleep(5)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun conflictingParentName() {
+    val source = FileSpec.builder("com.squareup.tacos", "A")
+      .addType(
+        TypeSpec.classBuilder("A")
+          .addType(
+            TypeSpec.classBuilder("B")
+              .addType(TypeSpec.classBuilder("Twin").build())
+              .addType(
+                TypeSpec.classBuilder("C")
+                  .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+                  .build(),
+              )
+              .build(),
+          )
+          .addType(
+            TypeSpec.classBuilder("Twin")
+              .addType(
+                TypeSpec.classBuilder("D")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class A {
+        |  public class B {
+        |    public class Twin
+        |
+        |    public class C {
+        |      public val d: A.Twin.D
+        |    }
+        |  }
+        |
+        |  public class Twin {
+        |    public class D
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun conflictingChildName() {
+    val source = FileSpec.builder("com.squareup.tacos", "A")
+      .addType(
+        TypeSpec.classBuilder("A")
+          .addType(
+            TypeSpec.classBuilder("B")
+              .addType(
+                TypeSpec.classBuilder("C")
+                  .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+                  .addType(TypeSpec.classBuilder("Twin").build())
+                  .build(),
+              )
+              .build(),
+          )
+          .addType(
+            TypeSpec.classBuilder("Twin")
+              .addType(
+                TypeSpec.classBuilder("D")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class A {
+        |  public class B {
+        |    public class C {
+        |      public val d: A.Twin.D
+        |
+        |      public class Twin
+        |    }
+        |  }
+        |
+        |  public class Twin {
+        |    public class D
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun conflictingNameOutOfScope() {
+    val source = FileSpec.builder("com.squareup.tacos", "A")
+      .addType(
+        TypeSpec.classBuilder("A")
+          .addType(
+            TypeSpec.classBuilder("B")
+              .addType(
+                TypeSpec.classBuilder("C")
+                  .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+                  .addType(
+                    TypeSpec.classBuilder("Nested")
+                      .addType(TypeSpec.classBuilder("Twin").build())
+                      .build(),
+                  )
+                  .build(),
+              )
+              .build(),
+          )
+          .addType(
+            TypeSpec.classBuilder("Twin")
+              .addType(
+                TypeSpec.classBuilder("D")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class A {
+        |  public class B {
+        |    public class C {
+        |      public val d: Twin.D
+        |
+        |      public class Nested {
+        |        public class Twin
+        |      }
+        |    }
+        |  }
+        |
+        |  public class Twin {
+        |    public class D
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nestedClassAndSuperclassShareName() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .superclass(ClassName("com.squareup.wire", "Message"))
+          .addType(
+            TypeSpec.classBuilder("Builder")
+              .superclass(ClassName("com.squareup.wire", "Message", "Builder"))
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.wire.Message
+        |
+        |public class Taco : Message() {
+        |  public class Builder : Message.Builder()
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /** https://github.com/square/javapoet/issues/366  */
+  @Test fun annotationIsNestedClass() {
+    val source = FileSpec.builder("com.squareup.tacos", "TestComponent")
+      .addType(
+        TypeSpec.classBuilder("TestComponent")
+          .addAnnotation(ClassName("dagger", "Component"))
+          .addType(
+            TypeSpec.classBuilder("Builder")
+              .addAnnotation(ClassName("dagger", "Component", "Builder"))
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import dagger.Component
+        |
+        |@Component
+        |public class TestComponent {
+        |  @Component.Builder
+        |  public class Builder
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun defaultPackage() {
+    val source = FileSpec.builder("", "HelloWorld")
+      .addType(
+        TypeSpec.classBuilder("HelloWorld")
+          .addFunction(
+            FunSpec.builder("main")
+              .addModifiers(KModifier.PUBLIC)
+              .addParameter("args", ARRAY.parameterizedBy(String::class.asClassName()))
+              .addCode("%T.out.println(%S);\n", System::class, "Hello World!")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |import java.lang.System
+        |import kotlin.Array
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public class HelloWorld {
+        |  public fun main(args: Array<String>): Unit {
+        |    System.out.println("Hello World!");
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun defaultPackageTypesAreImported() {
+    val source = FileSpec.builder("hello", "World")
+      .addType(
+        TypeSpec.classBuilder("World")
+          .addSuperinterface(ClassName("", "Test"))
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package hello
+        |
+        |import Test
+        |
+        |public class World : Test
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun topOfFileComment() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(TypeSpec.classBuilder("Taco").build())
+      .addFileComment("Generated %L by KotlinPoet. DO NOT EDIT!", "2015-01-13")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |// Generated 2015-01-13 by KotlinPoet. DO NOT EDIT!
+        |package com.squareup.tacos
+        |
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun emptyLinesInTopOfFileComment() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(TypeSpec.classBuilder("Taco").build())
+      .addFileComment("\nGENERATED FILE:\n\nDO NOT EDIT!\n")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |//
+        |// GENERATED FILE:
+        |//
+        |// DO NOT EDIT!
+        |//
+        |package com.squareup.tacos
+        |
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun packageClassConflictsWithNestedClass() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty("a", ClassName("com.squareup.tacos", "A"))
+          .addType(TypeSpec.classBuilder("A").build())
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Taco {
+        |  public val a: com.squareup.tacos.A
+        |
+        |  public class A
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleTypesInOneFile() {
+    val source = FileSpec.builder("com.squareup.tacos", "AB")
+      .addType(TypeSpec.classBuilder("A").build())
+      .addType(TypeSpec.classBuilder("B").build())
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class A
+        |
+        |public class B
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun simpleTypeAliases() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addTypeAlias(TypeAliasSpec.builder("Int8", Byte::class).build())
+      .addTypeAlias(
+        TypeAliasSpec.builder(
+          "FileTable",
+          Map::class.parameterizedBy(String::class, Int::class),
+        ).build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Byte
+        |import kotlin.Int
+        |import kotlin.String
+        |import kotlin.collections.Map
+        |
+        |public typealias Int8 = Byte
+        |
+        |public typealias FileTable = Map<String, Int>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun fileAnnotations() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class)
+          .useSiteTarget(FILE)
+          .addMember("%S", "TacoUtils")
+          .build(),
+      )
+      .addAnnotation(JvmMultifileClass::class)
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |@file:JvmName("TacoUtils")
+        |@file:JvmMultifileClass
+        |
+        |package com.squareup.tacos
+        |
+        |import kotlin.jvm.JvmMultifileClass
+        |import kotlin.jvm.JvmName
+        |
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun fileAnnotationMustHaveCorrectUseSiteTarget() {
+    val builder = FileSpec.builder("com.squareup.tacos", "Taco")
+    val annotation = AnnotationSpec.builder(JvmName::class)
+      .useSiteTarget(SET)
+      .addMember("value", "%S", "TacoUtils")
+      .build()
+    assertThrows<IllegalStateException> {
+      builder.addAnnotation(annotation)
+    }.hasMessageThat().isEqualTo("Use-site target SET not supported for file annotations.")
+  }
+
+  @Test fun escapeKeywordInPackageName() {
+    val source = FileSpec.builder("com.squareup.is.fun.in", "California")
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.`is`.`fun`.`in`
+        |
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun generalBuilderEqualityTest() {
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addAnnotation(JvmMultifileClass::class)
+      .addFileComment("Generated 2015-01-13 by KotlinPoet. DO NOT EDIT!")
+      .addImport("com.squareup.tacos.internal", "INGREDIENTS")
+      .addTypeAlias(TypeAliasSpec.builder("Int8", Byte::class).build())
+      .indent("  ")
+      .addFunction(
+        FunSpec.builder("defaultIngredients")
+          .addCode("println(INGREDIENTS)\n")
+          .build(),
+      )
+      .build()
+
+    assertThat(source.toBuilder().build()).isEqualTo(source)
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = FileSpec.builder("com.taco", "Taco")
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class.asClassName())
+          .useSiteTarget(FILE)
+          .addMember("name = %S", "JvmTaco")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+      .useSiteTarget(FILE)
+      .addMember("name = %S", "JavaTaco")
+      .build()
+    builder.annotations.clear()
+    builder.annotations.add(javaWord)
+
+    assertThat(builder.build().annotations).containsExactly(javaWord)
+  }
+
+  @Test fun modifyImports() {
+    val builder = FileSpec.builder("com.taco", "Taco")
+      .addImport("com.foo", "Foo")
+
+    val currentImports = builder.imports
+    builder.clearImports()
+    builder.addImport("com.foo", "Foo2")
+      .apply {
+        for (current in currentImports) {
+          addImport(current)
+        }
+      }
+      .indent("")
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+      package com.taco
+
+      import com.foo.Foo
+      import com.foo.Foo2
+
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun modifyMembers() {
+    val builder = FileSpec.builder("com.taco", "Taco")
+      .addFunction(FunSpec.builder("aFunction").build())
+      .addProperty(PropertySpec.builder("aProperty", INT).initializer("1").build())
+      .addTypeAlias(TypeAliasSpec.builder("ATypeAlias", INT).build())
+      .addType(TypeSpec.classBuilder("AClass").build())
+
+    builder.members.removeAll { it !is TypeSpec }
+
+    check(builder.build().members.all { it is TypeSpec })
+  }
+
+  @Test fun clearComment() {
+    val builder = FileSpec.builder("com.taco", "Taco")
+      .addFunction(FunSpec.builder("aFunction").build())
+      .addFileComment("Hello!")
+
+    builder.clearComment()
+      .addFileComment("Goodbye!")
+
+    assertThat(builder.build().comment.toString()).isEqualTo("Goodbye!")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/480
+  @Test fun defaultPackageMemberImport() {
+    val bigInteger = ClassName.bestGuess("bigInt.BigInteger")
+    val spec = FileSpec.builder("testsrc", "Test")
+      .addImport("", "bigInt")
+      .addFunction(
+        FunSpec.builder("add5ToInput")
+          .addParameter("input", Int::class)
+          .returns(bigInteger)
+          .addCode(
+            """
+               |val inputBigInt = bigInt(input)
+               |return inputBigInt.add(5)
+               |
+            """.trimMargin(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package testsrc
+      |
+      |import bigInt
+      |import bigInt.BigInteger
+      |import kotlin.Int
+      |
+      |public fun add5ToInput(input: Int): BigInteger {
+      |  val inputBigInt = bigInt(input)
+      |  return inputBigInt.add(5)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun longFilePackageName() {
+    val spec = FileSpec.builder("com.squareup.taco.enchilada.quesadillas.tamales.burritos.super.burritos.trying.to.get.a.really.large.packagename", "Test")
+      .addFunction(
+        FunSpec.builder("foo")
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package com.squareup.taco.enchilada.quesadillas.tamales.burritos.`super`.burritos.trying.to.`get`.a.really.large.packagename
+      |
+      |import kotlin.Unit
+      |
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importLongPackageName() {
+    val spec = FileSpec.builder("testsrc", "Test")
+      .addImport("a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength", "MyClass")
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package testsrc
+      |
+      |import a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength.MyClass
+      |
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importAliasedLongPackageName() {
+    val spec = FileSpec.builder("testsrc", "Test")
+      .addAliasedImport(ClassName("a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength", "MyClass"), "MyClassAlias")
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package testsrc
+      |
+      |import a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength.MyClass as MyClassAlias
+      |
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun longComment() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFileComment(
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+          "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+      |package com.squareup.tacos
+      |
+      |
+      """.trimMargin(),
+    )
+  }
+
+  class WackyKey
+  class OhNoThisDoesNotCompile
+
+  @Test fun longCommentWithTypes() {
+    val someLongParameterizedTypeName = typeNameOf<List<Map<in String, Collection<Map<WackyKey, out OhNoThisDoesNotCompile>>>>>()
+    val param = ParameterSpec.builder("foo", someLongParameterizedTypeName).build()
+    val someLongLambdaTypeName = LambdaTypeName.get(STRING, listOf(param), STRING).copy(suspending = true)
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("f1")
+          .addComment("this is a long line with a possibly long parameterized type with annotation: %T", someLongParameterizedTypeName)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("f2")
+          .addComment("this is a very very very very very very very very very very long line with a very long lambda type: %T", someLongLambdaTypeName)
+          .build(),
+      )
+      .build()
+
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.kotlinpoet.FileSpecTest
+      |import kotlin.String
+      |import kotlin.Unit
+      |import kotlin.collections.Collection
+      |import kotlin.collections.List
+      |import kotlin.collections.Map
+      |
+      |public fun f1(): Unit {
+      |  // this is a long line with a possibly long parameterized type with annotation: List<Map<in String, Collection<Map<FileSpecTest.WackyKey, out FileSpecTest.OhNoThisDoesNotCompile>>>>
+      |}
+      |
+      |public fun f2(): Unit {
+      |  // this is a very very very very very very very very very very long line with a very long lambda type: suspend String.(foo: List<Map<in String, Collection<Map<FileSpecTest.WackyKey, out FileSpecTest.OhNoThisDoesNotCompile>>>>) -> String
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun simpleScriptTest() {
+    val spec = FileSpec.scriptBuilder("Taco")
+      .addProperty(PropertySpec.builder("prop", String::class).initializer("\"hi\"").build())
+      .addCode("\n")
+      .addStatement("println(%S)", "hello!")
+      .addCode("\n")
+      .addFunction(
+        FunSpec.builder("localFun")
+          .build(),
+      )
+      .addCode("\n")
+      .addType(TypeSpec.classBuilder("Yay").build())
+      .addCode("\n")
+      .addStatement("val yayInstance = Yay()")
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |val prop: String = "hi"
+      |
+      |println("hello!")
+      |
+      |public fun localFun(): Unit {
+      |}
+      |
+      |public class Yay
+      |
+      |val yayInstance = Yay()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun defaultImports() {
+    val spec = FileSpec.scriptBuilder("Taco")
+      .addProperty(PropertySpec.builder("prop0", STRING.copy(nullable = true)).initializer("null").build())
+      .addProperty(PropertySpec.builder("prop1", INT.copy(nullable = true)).initializer("null").build())
+      .addProperty(PropertySpec.builder("prop2", typeNameOf<Map<String, Any>?>()).initializer("null").build())
+      .addProperty(PropertySpec.builder("prop3", typeNameOf<Callable<String>?>()).initializer("null").build())
+      .addProperty(PropertySpec.builder("prop4", typeNameOf<Function<Int, Int>?>()).initializer("null").build())
+      .addKotlinDefaultImports()
+      .addDefaultPackageImport("java.util.function")
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |import java.util.concurrent.Callable
+      |
+      |val prop0: String? = null
+      |val prop1: Int? = null
+      |val prop2: Map<String, Any>? = null
+      |val prop3: @FunctionalInterface Callable<String>? = null
+      |val prop4: @FunctionalInterface Function<Int, Int>? = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classNameFactory() {
+    val className = ClassName("com.example", "Example")
+    val spec = FileSpec.builder(className).build()
+    assertThat(spec.packageName).isEqualTo(className.packageName)
+    assertThat(spec.name).isEqualTo(className.simpleName)
+  }
+
+  @Test fun classNameFactoryIllegalArgumentExceptionOnNestedType() {
+    val className = ClassName("com.example", "Example", "Nested")
+    assertThrows<IllegalArgumentException> {
+      FileSpec.builder(className)
+    }
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt
new file mode 100644
index 0000000..3980709
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.Files
+import java.util.Date
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.rules.TemporaryFolder
+
+class FileWritingTest {
+  // Used for testing java.io File behavior.
+  @JvmField @Rule
+  val tmp = TemporaryFolder()
+
+  // Used for testing java.nio.file Path behavior.
+  private val fs = Jimfs.newFileSystem(Configuration.unix())
+  private val fsRoot = fs.rootDirectories.iterator().next()
+
+  // Used for testing annotation processor Filer behavior.
+  private val filer = TestFiler(fs, fsRoot)
+
+  @Test fun pathNotDirectory() {
+    val type = TypeSpec.classBuilder("Test").build()
+    val source = FileSpec.get("example", type)
+    val path = fs.getPath("/foo/bar")
+    Files.createDirectories(path.parent)
+    Files.createFile(path)
+    assertThrows<IllegalArgumentException> {
+      source.writeTo(path)
+    }.hasMessageThat().isEqualTo("path /foo/bar exists but is not a directory.")
+  }
+
+  @Test fun fileNotDirectory() {
+    val type = TypeSpec.classBuilder("Test").build()
+    val source = FileSpec.get("example", type)
+    val file = File(tmp.newFolder("foo"), "bar")
+    file.createNewFile()
+    assertThrows<IllegalArgumentException> {
+      source.writeTo(file)
+    }.hasMessageThat().isEqualTo("path ${file.path} exists but is not a directory.")
+  }
+
+  @Test fun filerDefaultPackage() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("", type).writeTo(filer)
+
+    val testPath = fsRoot.resolve("Test.kt")
+    assertThat(Files.exists(testPath)).isTrue()
+  }
+
+  @Test fun pathDefaultPackage() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("", type).writeTo(fsRoot)
+
+    val testPath = fsRoot.resolve("Test.kt")
+    assertThat(Files.exists(testPath)).isTrue()
+  }
+
+  @Test fun pathDefaultPackageWithSubdirectory() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("", type).writeTo(fsRoot.resolve("sub"))
+
+    val testPath = fsRoot.resolve("sub/Test.kt")
+    assertThat(Files.exists(testPath)).isTrue()
+  }
+
+  @Test fun fileDefaultPackage() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("", type).writeTo(tmp.root)
+
+    val testFile = File(tmp.root, "Test.kt")
+    assertThat(testFile.exists()).isTrue()
+  }
+
+  @Test fun pathNestedClasses() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("foo", type).writeTo(fsRoot)
+    FileSpec.get("foo.bar", type).writeTo(fsRoot)
+    FileSpec.get("foo.bar.baz", type).writeTo(fsRoot)
+
+    val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+    val barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.kt"))
+    val bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.kt"))
+    assertThat(Files.exists(fooPath)).isTrue()
+    assertThat(Files.exists(barPath)).isTrue()
+    assertThat(Files.exists(bazPath)).isTrue()
+  }
+
+  @Test fun fileNestedClasses() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("foo", type).writeTo(tmp.root)
+    FileSpec.get("foo.bar", type).writeTo(tmp.root)
+    FileSpec.get("foo.bar.baz", type).writeTo(tmp.root)
+
+    val fooDir = File(tmp.root, "foo")
+    val fooFile = File(fooDir, "Test.kt")
+    val barDir = File(fooDir, "bar")
+    val barFile = File(barDir, "Test.kt")
+    val bazDir = File(barDir, "baz")
+    val bazFile = File(bazDir, "Test.kt")
+    assertThat(fooFile.exists()).isTrue()
+    assertThat(barFile.exists()).isTrue()
+    assertThat(bazFile.exists()).isTrue()
+  }
+
+  @Test fun filerNestedClasses() {
+    val type = TypeSpec.classBuilder("Test").build()
+    FileSpec.get("foo", type).writeTo(filer)
+    FileSpec.get("foo.bar", type).writeTo(filer)
+    FileSpec.get("foo.bar.baz", type).writeTo(filer)
+
+    val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+    val barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.kt"))
+    val bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.kt"))
+    assertThat(Files.exists(fooPath)).isTrue()
+    assertThat(Files.exists(barPath)).isTrue()
+    assertThat(Files.exists(bazPath)).isTrue()
+  }
+
+  @Suppress("LocalVariableName")
+  @Test
+  fun filerPassesOriginatingElements() {
+    // TypeSpecs
+    val element1_1 = FakeElement()
+    val test1 = TypeSpec.classBuilder("Test1")
+      .addOriginatingElement(element1_1)
+      .build()
+
+    val element2_1 = FakeElement()
+    val element2_2 = FakeElement()
+    val test2 = TypeSpec.classBuilder("Test2")
+      .addOriginatingElement(element2_1)
+      .addOriginatingElement(element2_2)
+      .build()
+
+    // FunSpecs
+    val element3_1 = FakeElement()
+    val element3_2 = FakeElement()
+    val test3 = FunSpec.builder("fun3")
+      .addOriginatingElement(element3_1)
+      .addOriginatingElement(element3_2)
+      .build()
+
+    // PropertySpecs
+    val element4_1 = FakeElement()
+    val element4_2 = FakeElement()
+    val test4 = PropertySpec.builder("property4", String::class)
+      .addOriginatingElement(element4_1)
+      .addOriginatingElement(element4_2)
+      .build()
+
+    FileSpec.get("example", test1).writeTo(filer)
+    FileSpec.get("example", test2).writeTo(filer)
+    FileSpec.builder("example", "Test3")
+      .addFunction(test3)
+      .build()
+      .writeTo(filer)
+    FileSpec.builder("example", "Test4")
+      .addProperty(test4)
+      .build()
+      .writeTo(filer)
+
+    // Mixed
+    FileSpec.builder("example", "Mixed")
+      .addType(test1)
+      .addType(test2)
+      .addFunction(test3)
+      .addProperty(test4)
+      .build()
+      .writeTo(filer)
+
+    val testPath1 = fsRoot.resolve(fs.getPath("example", "Test1.kt"))
+    assertThat(filer.getOriginatingElements(testPath1)).containsExactly(element1_1)
+    val testPath2 = fsRoot.resolve(fs.getPath("example", "Test2.kt"))
+    assertThat(filer.getOriginatingElements(testPath2)).containsExactly(element2_1, element2_2)
+    val testPath3 = fsRoot.resolve(fs.getPath("example", "Test3.kt"))
+    assertThat(filer.getOriginatingElements(testPath3)).containsExactly(element3_1, element3_2)
+    val testPath4 = fsRoot.resolve(fs.getPath("example", "Test4.kt"))
+    assertThat(filer.getOriginatingElements(testPath4)).containsExactly(element4_1, element4_2)
+
+    val mixed = fsRoot.resolve(fs.getPath("example", "Mixed.kt"))
+    assertThat(filer.getOriginatingElements(mixed)).containsExactly(
+      element1_1,
+      element2_1,
+      element2_2,
+      element3_1,
+      element3_2,
+      element4_1,
+      element4_2,
+    )
+  }
+
+  @Test fun filerPassesOnlyUniqueOriginatingElements() {
+    val element1 = FakeElement()
+    val fun1 = FunSpec.builder("test1")
+      .addOriginatingElement(element1)
+      .build()
+
+    val element2 = FakeElement()
+    val fun2 = FunSpec.builder("test2")
+      .addOriginatingElement(element1)
+      .addOriginatingElement(element2)
+      .build()
+
+    FileSpec.builder("example", "File")
+      .addFunction(fun1)
+      .addFunction(fun2)
+      .build()
+      .writeTo(filer)
+
+    val file = fsRoot.resolve(fs.getPath("example", "File.kt"))
+    assertThat(filer.getOriginatingElements(file)).containsExactly(element1, element2)
+  }
+
+  @Test fun filerClassesWithTabIndent() {
+    val test = TypeSpec.classBuilder("Test")
+      .addProperty("madeFreshDate", Date::class)
+      .addFunction(
+        FunSpec.builder("main")
+          .addModifiers(KModifier.PUBLIC)
+          .addParameter("args", Array<String>::class.java)
+          .addCode("%T.out.println(%S);\n", System::class, "Hello World!")
+          .build(),
+      )
+      .build()
+    FileSpec.builder("foo", "Test")
+      .addType(test)
+      .indent("\t")
+      .build()
+      .writeTo(filer)
+
+    val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+    assertThat(Files.exists(fooPath)).isTrue()
+    val source = String(Files.readAllBytes(fooPath))
+
+    assertThat(source).isEqualTo(
+      """
+        |package foo
+        |
+        |import java.lang.String
+        |import java.lang.System
+        |import java.util.Date
+        |import kotlin.Array
+        |import kotlin.Unit
+        |
+        |public class Test {
+        |${"\t"}public val madeFreshDate: Date
+        |
+        |${"\t"}public fun main(args: Array<String>): Unit {
+        |${"\t\t"}System.out.println("Hello World!");
+        |${"\t"}}
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /**
+   * This test confirms that KotlinPoet ignores the host charset and always uses UTF-8. The host
+   * charset is customized with `-Dfile.encoding=ISO-8859-1`.
+   */
+  @Test fun fileIsUtf8() {
+    val source = FileSpec.builder("foo", "Taco")
+      .addType(TypeSpec.classBuilder("Taco").build())
+      .addFileComment("Pi\u00f1ata\u00a1")
+      .build()
+    source.writeTo(fsRoot)
+
+    val fooPath = fsRoot.resolve(fs.getPath("foo", "Taco.kt"))
+    assertThat(String(Files.readAllBytes(fooPath), UTF_8)).isEqualTo(
+      """
+        |// Piñata¡
+        |package foo
+        |
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun fileWithKeywordName() {
+    val type = TypeSpec.classBuilder("fun").build()
+    FileSpec.get("", type).writeTo(filer)
+
+    val testPath = fsRoot.resolve("fun.kt")
+    assertThat(Files.exists(testPath)).isTrue()
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt
new file mode 100644
index 0000000..1376caf
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.collect.Iterables.getOnlyElement
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Closeable
+import java.io.IOException
+import java.util.concurrent.Callable
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.util.ElementFilter.methodsIn
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import org.junit.Rule
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class FunSpecTest {
+  @Rule @JvmField
+  val compilation = CompilationRule()
+
+  private lateinit var elements: Elements
+  private lateinit var types: Types
+
+  @BeforeTest fun setUp() {
+    elements = compilation.elements
+    types = compilation.types
+  }
+
+  private fun getElement(`class`: Class<*>): TypeElement {
+    return elements.getTypeElement(`class`.canonicalName)
+  }
+
+  private fun findFirst(elements: Collection<ExecutableElement>, name: String) =
+    elements.firstOrNull { it.simpleName.toString() == name }
+      ?: throw IllegalArgumentException("$name not found in $elements")
+
+  @Target(AnnotationTarget.VALUE_PARAMETER)
+  internal annotation class Nullable
+
+  internal abstract class Everything {
+    @Deprecated("")
+    @Throws(IOException::class, SecurityException::class)
+    protected abstract fun <T> everything(
+      @Nullable thing: String,
+      things: List<T>,
+    ): Runnable where T : Runnable, T : Closeable
+  }
+
+  internal abstract class HasAnnotation {
+    abstract override fun toString(): String
+  }
+
+  internal interface ExtendsOthers : Callable<Int>, Comparable<Long>
+
+  annotation class TestAnnotation
+
+  abstract class InvalidOverrideMethods {
+    fun finalMethod() {
+    }
+
+    private fun privateMethod() {
+    }
+
+    companion object {
+      @JvmStatic open fun staticMethod() {
+      }
+    }
+  }
+
+  @Test fun overrideEverything() {
+    val classElement = getElement(Everything::class.java)
+    val methodElement = getOnlyElement(methodsIn(classElement.enclosedElements))
+    val funSpec = FunSpec.overriding(methodElement).build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+        |@kotlin.jvm.Throws(java.io.IOException::class, java.lang.SecurityException::class)
+        |protected override fun <T> everything(arg0: java.lang.String, arg1: java.util.List<out T>): java.lang.Runnable where T : java.lang.Runnable, T : java.io.Closeable {
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun overrideDoesNotCopyOverrideAnnotation() {
+    val classElement = getElement(HasAnnotation::class.java)
+    val exec = getOnlyElement(methodsIn(classElement.enclosedElements))
+    val funSpec = FunSpec.overriding(exec).build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+        |public override fun toString(): java.lang.String {
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun overrideExtendsOthersWorksWithActualTypeParameters() {
+    val classElement = getElement(ExtendsOthers::class.java)
+    val classType = classElement.asType() as DeclaredType
+    val methods = methodsIn(elements.getAllMembers(classElement))
+    var exec = findFirst(methods, "call")
+    var funSpec = FunSpec.overriding(exec, classType, types).build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+        |@kotlin.jvm.Throws(java.lang.Exception::class)
+        |public override fun call(): java.lang.Integer {
+        |}
+        |
+      """.trimMargin(),
+    )
+    exec = findFirst(methods, "compareTo")
+    funSpec = FunSpec.overriding(exec, classType, types).build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+        |public override fun compareTo(arg0: java.lang.Long): kotlin.Int {
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun overrideInvalidModifiers() {
+    val classElement = getElement(InvalidOverrideMethods::class.java)
+    val methods = methodsIn(elements.getAllMembers(classElement))
+
+    assertThrows<IllegalArgumentException> {
+      FunSpec.overriding(findFirst(methods, "finalMethod"))
+    }.hasMessageThat().isEqualTo("cannot override method with modifiers: [public, final]")
+
+    assertThrows<IllegalArgumentException> {
+      FunSpec.overriding(findFirst(methods, "privateMethod"))
+    }.hasMessageThat().isEqualTo("cannot override method with modifiers: [private, final]")
+
+    assertThrows<IllegalArgumentException> {
+      FunSpec.overriding(findFirst(methods, "staticMethod"))
+    }.hasMessageThat().isEqualTo("cannot override method with modifiers: [public, static]")
+  }
+
+  @Test fun nullableParam() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(
+        ParameterSpec
+          .builder("string", String::class.asTypeName().copy(nullable = true))
+          .build(),
+      )
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(string: kotlin.String?): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nullableReturnType() {
+    val funSpec = FunSpec.builder("foo")
+      .returns(String::class.asTypeName().copy(nullable = true))
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.String? {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun returnsUnitWithoutExpressionBody() {
+    val funSpec = FunSpec.builder("foo")
+      .returns(Unit::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun returnsUnitWithExpressionBody() {
+    val funSpec = FunSpec.builder("foo")
+      .returns(Unit::class)
+      .addStatement("return bar()")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.Unit = bar()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun returnsLongExpression() {
+    val funSpec = FunSpec.builder("foo")
+      .returns(String::class)
+      .addStatement("val placeholder = 1")
+      .addStatement("return %S", "Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong")
+      .build()
+    val sb = StringBuilder()
+    // The FunSpec#toString columnLimit is Integer.MAX_VALUE,
+    // It will not cause problems with returns long expressions.
+    CodeWriter(sb).use {
+      funSpec.emit(
+        codeWriter = it,
+        enclosingName = null,
+        implicitModifiers = setOf(KModifier.PUBLIC),
+        includeKdocTags = false,
+      )
+    }
+    assertThat(sb.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.String {
+      |  val placeholder = 1
+      |  return "Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamWithKdoc() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(
+        ParameterSpec.builder("string", String::class.asTypeName())
+          .addKdoc("A string parameter.")
+          .build(),
+      )
+      .addParameter(
+        ParameterSpec.builder("number", Int::class.asTypeName())
+          .addKdoc("A number with a multi-line doc comment.\nYes,\nthese\nthings\nhappen.")
+          .build(),
+      )
+      .addParameter(ParameterSpec.builder("nodoc", Boolean::class.asTypeName()).build())
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @param string A string parameter.
+      | * @param number A number with a multi-line doc comment.
+      | * Yes,
+      | * these
+      | * things
+      | * happen.
+      | */
+      |public fun foo(
+      |  string: kotlin.String,
+      |  number: kotlin.Int,
+      |  nodoc: kotlin.Boolean,
+      |): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamWithKdocToBuilder() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(
+        ParameterSpec.builder("string", String::class.asTypeName())
+          .addKdoc("A string parameter.")
+          .build()
+          .toBuilder()
+          .addKdoc(" This is non null")
+          .build(),
+      )
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @param string A string parameter. This is non null
+      | */
+      |public fun foo(string: kotlin.String): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun originatingElementToBuilder() {
+    val originatingElement = FakeElement()
+    val funSpec = FunSpec.builder("foo")
+      .addOriginatingElement(originatingElement)
+      .build()
+
+    val newSpec = funSpec.toBuilder().build()
+    assertThat(newSpec.originatingElements).containsExactly(originatingElement)
+  }
+
+  @Test fun functionParamWithKdocAndReturnKdoc() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(
+        ParameterSpec.builder("string", String::class)
+          .addKdoc("A string parameter.")
+          .build(),
+      )
+      .addParameter(ParameterSpec.builder("nodoc", Boolean::class).build())
+      .returns(String::class, kdoc = "the foo.")
+      .addStatement("return %S", "foo")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @param string A string parameter.
+      | * @return the foo.
+      | */
+      |public fun foo(string: kotlin.String, nodoc: kotlin.Boolean): kotlin.String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithModifiedReturnKdoc() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter("nodoc", Boolean::class)
+      .returns(String::class, kdoc = "the foo.")
+      .addStatement("return %S", "foo")
+      .build()
+      .toBuilder()
+      .returns(String::class, kdoc = "the modified foo.")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @return the modified foo.
+      | */
+      |public fun foo(nodoc: kotlin.Boolean): kotlin.String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithThrows() {
+    val funSpec = FunSpec.builder("foo")
+      .addStatement("throw %T()", AssertionError::class)
+      .returns(NOTHING)
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.Nothing = throw java.lang.AssertionError()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithWordThrowDoesntConvertToExpressionFunction() {
+    val throwSomethingElseFun = FunSpec.builder("throwOrDoSomethingElse")
+      .build()
+
+    val funSpec = FunSpec.builder("foo")
+      .addStatement("%N()", throwSomethingElseFun)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.Unit {
+      |  throwOrDoSomethingElse()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun expressionBodyIsDetectedReturnWithNonBreakingSpace() {
+    val funSpec = FunSpec.builder("foo")
+      .addStatement("return·1")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo() = 1
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun expressionBodyIsDetectedThrowWithNonBreakingSpace() {
+    val funSpec = FunSpec.builder("foo")
+      .addStatement("throw·%T()", AssertionError::class)
+      .returns(NOTHING)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(): kotlin.Nothing = throw java.lang.AssertionError()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithReturnKDocAndMainKdoc() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter("nodoc", Boolean::class)
+      .returns(String::class, kdoc = "the foo.")
+      .addStatement("return %S", "foo")
+      .addKdoc("Do the foo")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * Do the foo
+      | *
+      | * @return the foo.
+      | */
+      |public fun foo(nodoc: kotlin.Boolean): kotlin.String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamNoLambdaParam() {
+    val unitType = UNIT
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", LambdaTypeName.get(returnType = unitType)).build())
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: () -> kotlin.Unit): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithReturnKDoc() {
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", LambdaTypeName.get(returnType = UNIT)).build())
+      .returns(String::class, CodeBlock.of("the foo."))
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @return the foo.
+      | */
+      |public fun foo(f: () -> kotlin.Unit): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamNoLambdaParamWithReceiver() {
+    val unitType = UNIT
+    val lambdaTypeName = LambdaTypeName.get(receiver = INT, returnType = unitType)
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: kotlin.Int.() -> kotlin.Unit): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithContextReceiver() {
+    val stringType = STRING
+    val funSpec = FunSpec.builder("foo")
+      .contextReceivers(stringType)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |public fun foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithMultipleContextReceivers() {
+    val stringType = STRING
+    val intType = INT
+    val booleanType = BOOLEAN
+    val funSpec = FunSpec.builder("foo")
+      .contextReceivers(stringType, intType, booleanType)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String, kotlin.Int, kotlin.Boolean)
+      |public fun foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithGenericContextReceiver() {
+    val genericType = TypeVariableName("T")
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(genericType)
+      .contextReceivers(genericType)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |context(T)
+      |public fun <T> foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedFunctionWithContextReceiver() {
+    val funSpec = FunSpec.builder("foo")
+      .addAnnotation(AnnotationSpec.get(TestAnnotation()))
+      .contextReceivers(STRING)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation
+      |public fun foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionWithAnnotatedContextReceiver() {
+    val genericType = STRING.copy(annotations = listOf(AnnotationSpec.get(TestAnnotation())))
+    val funSpec = FunSpec.builder("foo")
+      .contextReceivers(genericType)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |context(@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation kotlin.String)
+      |public fun foo(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun constructorWithContextReceiver() {
+    assertThrows<IllegalStateException> {
+      FunSpec.constructorBuilder()
+        .contextReceivers(STRING)
+    }.hasMessageThat().isEqualTo("constructors cannot have context receivers")
+  }
+
+  @Test fun accessorWithContextReceiver() {
+    assertThrows<IllegalStateException> {
+      FunSpec.getterBuilder()
+        .contextReceivers(STRING)
+    }.hasMessageThat().isEqualTo("$GETTER cannot have context receivers")
+
+    assertThrows<IllegalStateException> {
+      FunSpec.setterBuilder()
+        .contextReceivers(STRING)
+    }.hasMessageThat().isEqualTo("$SETTER cannot have context receivers")
+  }
+
+  @Test fun functionParamSingleLambdaParam() {
+    val unitType = UNIT
+    val booleanType = BOOLEAN
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(
+        ParameterSpec.builder(
+          "f",
+          LambdaTypeName.get(
+            parameters = arrayOf(booleanType),
+            returnType = unitType,
+          ),
+        )
+          .build(),
+      )
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: (kotlin.Boolean) -> kotlin.Unit): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamMultipleLambdaParam() {
+    val unitType = UNIT
+    val booleanType = BOOLEAN
+    val stringType = String::class.asClassName()
+    val lambdaType = LambdaTypeName.get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", lambdaType).build())
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: (kotlin.Boolean, kotlin.String) -> kotlin.Unit): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamMultipleLambdaParamNullableLambda() {
+    val unitType = Unit::class.asClassName()
+    val booleanType = Boolean::class.asClassName()
+    val stringType = String::class.asClassName()
+    val lambdaTypeName = LambdaTypeName
+      .get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+      .copy(nullable = true)
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: ((kotlin.Boolean, kotlin.String) -> kotlin.Unit)?): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun functionParamMultipleNullableLambdaParam() {
+    val unitType = Unit::class.asClassName()
+    val booleanType = Boolean::class.asClassName()
+    val stringType = String::class.asClassName().copy(nullable = true)
+    val lambdaTypeName = LambdaTypeName
+      .get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+      .copy(nullable = true)
+    val funSpec = FunSpec.builder("foo")
+      .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+      .returns(String::class)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun foo(f: ((kotlin.Boolean, kotlin.String?) -> kotlin.Unit)?): kotlin.String {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun setterWithPublicModifier() {
+    val funSpec = FunSpec.setterBuilder()
+      .addParameter("value", String::class.asClassName())
+      .addStatement("this.value = this.value")
+      .addModifiers(KModifier.PUBLIC)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public set(`value`) {
+      |  this.value = this.value
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getterWithPublicModifier() {
+    val funSpec = FunSpec.getterBuilder()
+      .addStatement("return value")
+      .addModifiers(KModifier.PUBLIC)
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public get() = value
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // This does not produce correct Kotlin, but it does at least verify that we do not drop the
+  // explicitly specified public modifier.
+  @Test fun methodWithMultipleVisibilityModifiers() {
+    val funSpec =
+      FunSpec.builder("myMethod")
+        .addModifiers(KModifier.PUBLIC, KModifier.INTERNAL, KModifier.PRIVATE)
+        .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public private internal fun myMethod(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun methodWithRepeatedVisibilityModifier() {
+    val funSpec =
+      FunSpec.builder("myMethod")
+        .addModifiers(KModifier.PUBLIC, KModifier.PUBLIC, KModifier.PUBLIC)
+        .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun myMethod(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun thisConstructorDelegate() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("list", List::class.parameterizedBy(Int::class))
+      .callThisConstructor("list[0]", "list[1]")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public constructor(list: kotlin.collections.List<kotlin.Int>) : this(list[0], list[1])
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun superConstructorDelegate() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("list", List::class.parameterizedBy(Int::class))
+      .callSuperConstructor("list[0]", "list[1]")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public constructor(list: kotlin.collections.List<kotlin.Int>) : super(list[0], list[1])
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun emptyConstructorDelegate() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("a", Int::class)
+      .callThisConstructor()
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public constructor(a: kotlin.Int) : this()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun constructorDelegateWithBody() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("a", Int::class)
+      .callThisConstructor("a")
+      .addStatement("println()")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public constructor(a: kotlin.Int) : this(a) {
+      |  println()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun addingDelegateParametersToNonConstructorForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.builder("main")
+        .callThisConstructor("a", "b", "c")
+    }.hasMessageThat().isEqualTo("only constructors can delegate to other constructors!")
+  }
+
+  @Test fun emptySecondaryConstructor() {
+    val constructorSpec = FunSpec.constructorBuilder()
+      .addParameter("a", Int::class)
+      .build()
+
+    assertThat(constructorSpec.toString()).isEqualTo(
+      """
+      |public constructor(a: kotlin.Int)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun reifiedTypesOnNonInlineFunctionsForbidden() {
+    assertThrows<IllegalArgumentException> {
+      FunSpec.builder("foo")
+        .addTypeVariable(TypeVariableName("T").copy(reified = true))
+        .build()
+    }.hasMessageThat().isEqualTo("only type parameters of inline functions can be reified!")
+  }
+
+  @Test fun equalsAndHashCode() {
+    var a = FunSpec.constructorBuilder().build()
+    var b = FunSpec.constructorBuilder().build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = FunSpec.builder("taco").build()
+    b = FunSpec.builder("taco").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    val classElement = getElement(Everything::class.java)
+    val methodElement = getOnlyElement(methodsIn(classElement.enclosedElements))
+    a = FunSpec.overriding(methodElement).build()
+    b = FunSpec.overriding(methodElement).build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun escapeKeywordInFunctionName() {
+    val funSpec = FunSpec.builder("if")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun `if`(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapePunctuationInFunctionName() {
+    val funSpec = FunSpec.builder("with-hyphen")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun `with-hyphen`(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun generalBuilderEqualityTest() {
+    val funSpec = FunSpec.Builder("getConfig")
+      .addKdoc("Fix me")
+      .addAnnotation(
+        AnnotationSpec.builder(SuppressWarnings::class)
+          .build(),
+      )
+      .addModifiers(KModifier.PROTECTED)
+      .addTypeVariable(TypeVariableName("T"))
+      .receiver(String::class)
+      .returns(String::class)
+      .addParameter(
+        ParameterSpec.builder("config", String::class)
+          .build(),
+      )
+      .addParameter(
+        ParameterSpec.builder("override", TypeVariableName("T"))
+          .build(),
+      )
+      .beginControlFlow("return when")
+      .addStatement("    override is String -> config + override")
+      .addStatement("    else -> config + %S", "{ttl:500}")
+      .endControlFlow()
+      .build()
+
+    assertThat(funSpec.toBuilder().build()).isEqualTo(funSpec)
+  }
+
+  @Test fun receiverWithKdoc() {
+    val funSpec = FunSpec.builder("toBar")
+      .receiver(String::class, kdoc = "the string to transform.")
+      .returns(String::class)
+      .addStatement("return %S", "bar")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @receiver the string to transform.
+      | */
+      |public fun kotlin.String.toBar(): kotlin.String = "bar"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun receiverWithKdocAndMainKDoc() {
+    val funSpec = FunSpec.builder("toBar")
+      .receiver(String::class, kdoc = "the string to transform.")
+      .returns(String::class)
+      .addKdoc("%L", "Converts to bar")
+      .addStatement("return %S", "bar")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * Converts to bar
+      | *
+      | * @receiver the string to transform.
+      | */
+      |public fun kotlin.String.toBar(): kotlin.String = "bar"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun withAllKdocTags() {
+    val funSpec = FunSpec.builder("charAt")
+      .receiver(String::class, kdoc = "the string you want the char from.")
+      .returns(Char::class, kdoc = "The char at the given [position].")
+      .addParameter(
+        ParameterSpec.builder("position", Int::class)
+          .addKdoc("the index of the character that is returned.")
+          .build(),
+      )
+      .addKdoc("Returns the character at the given [position].\n\n")
+      .addStatement("return -1")
+      .build()
+
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * Returns the character at the given [position].
+      | *
+      | * @receiver the string you want the char from.
+      | * @param position the index of the character that is returned.
+      | * @return The char at the given [position].
+      | */
+      |public fun kotlin.String.charAt(position: kotlin.Int): kotlin.Char = -1
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun constructorBuilderEqualityTest() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("list", List::class.parameterizedBy(Int::class))
+      .callThisConstructor("list[0]", "list[1]")
+      .build()
+
+    assertThat(funSpec.toBuilder().build()).isEqualTo(funSpec)
+  }
+
+  // https://github.com/square/kotlinpoet/issues/398
+  @Test fun changingDelegateConstructorOverridesArgs() {
+    val funSpec = FunSpec.constructorBuilder()
+      .addParameter("values", List::class.parameterizedBy(String::class))
+      .callSuperConstructor("values")
+      .build()
+    val updatedFunSpec = funSpec.toBuilder()
+      .callSuperConstructor("values.toImmutableList()")
+      .build()
+    assertThat(updatedFunSpec.toString()).isEqualTo(
+      """
+      |public constructor(values: kotlin.collections.List<kotlin.String>) : super(values.toImmutableList())
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun modifyModifiers() {
+    val builder = FunSpec.builder("taco")
+      .addModifiers(KModifier.PRIVATE)
+
+    builder.modifiers.clear()
+    builder.modifiers.add(KModifier.INTERNAL)
+
+    assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = FunSpec.builder("taco")
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class.asClassName())
+          .addMember("name = %S", "jvmWord")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+      .addMember("name = %S", "javaWord")
+      .build()
+    builder.annotations.clear()
+    builder.annotations.add(javaWord)
+
+    assertThat(builder.build().annotations).containsExactly(javaWord)
+  }
+
+  @Test fun modifyTypeVariableNames() {
+    val builder = FunSpec.builder("taco")
+      .addTypeVariable(TypeVariableName("V"))
+
+    val tVar = TypeVariableName("T")
+    builder.typeVariables.clear()
+    builder.typeVariables.add(tVar)
+
+    assertThat(builder.build().typeVariables).containsExactly(tVar)
+  }
+
+  @Test fun modifyParameters() {
+    val builder = FunSpec.builder("taco")
+      .addParameter(ParameterSpec.builder("topping", String::class.asClassName()).build())
+
+    val seasoning = ParameterSpec.builder("seasoning", String::class.asClassName()).build()
+    builder.parameters.clear()
+    builder.parameters.add(seasoning)
+
+    assertThat(builder.build().parameters).containsExactly(seasoning)
+  }
+
+  @Test fun jvmStaticModifier() {
+    val builder = FunSpec.builder("staticMethod")
+    builder.jvmModifiers(listOf(Modifier.STATIC))
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+      |@kotlin.jvm.JvmStatic
+      |internal fun staticMethod(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmFinalModifier() {
+    val builder = FunSpec.builder("finalMethod")
+    builder.jvmModifiers(listOf(Modifier.FINAL))
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+      |internal final fun finalMethod(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmSynchronizedModifier() {
+    val builder = FunSpec.builder("synchronizedMethod")
+    builder.jvmModifiers(listOf(Modifier.SYNCHRONIZED))
+
+    assertThat(builder.build().toString()).isEqualTo(
+      """
+      |@kotlin.jvm.Synchronized
+      |internal fun synchronizedMethod(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun ensureTrailingNewline() {
+    val methodSpec = FunSpec.builder("function")
+      .addCode("codeWithNoNewline()")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public fun function(): kotlin.Unit {
+      |  codeWithNoNewline()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  /** Ensures that we don't add a duplicate newline if one is already present.  */
+  @Test fun ensureTrailingNewlineWithExistingNewline() {
+    val methodSpec = FunSpec.builder("function")
+      .addCode("codeWithNoNewline()\n") // Have a newline already, so ensure we're not adding one
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public fun function(): kotlin.Unit {
+      |  codeWithNoNewline()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/947
+  @Test fun ensureTrailingNewlineWithExpressionBody() {
+    val methodSpec = FunSpec.builder("function")
+      .addCode("return codeWithNoNewline()")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public fun function() = codeWithNoNewline()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun ensureTrailingNewlineWithExpressionBodyAndExistingNewline() {
+    val methodSpec = FunSpec.builder("function")
+      .addCode("return codeWithNoNewline()\n") // Have a newline already, so ensure we're not adding one
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |public fun function() = codeWithNoNewline()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun ensureKdocTrailingNewline() {
+    val methodSpec = FunSpec.builder("function")
+      .addKdoc("This is a comment with no initial newline")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * This is a comment with no initial newline
+      | */
+      |public fun function(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  /** Ensures that we don't add a duplicate newline if one is already present.  */
+  @Test fun ensureKdocTrailingNewlineWithExistingNewline() {
+    val methodSpec = FunSpec.builder("function")
+      .addKdoc("This is a comment with an initial newline\n")
+      .build()
+
+    assertThat(methodSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * This is a comment with an initial newline
+      | */
+      |public fun function(): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedLambdaReceiverType() {
+    val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+    val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+    val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .receiver(type)
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun (@Annotation () -> Unit).foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedLambdaReturnType() {
+    val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+    val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+    val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .returns(type)
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun foo(): @Annotation () -> Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java
new file mode 100644
index 0000000..c48b3a4
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java
@@ -0,0 +1,18 @@
+package com.squareup.kotlinpoet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue;
+
+@AnnotationWithArrayValue({
+    Object.class, Boolean.class
+})
+public class JavaClassWithArrayValueAnnotation {
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface AnnotationWithArrayValue {
+    Class[] value();
+  }
+
+}
\ No newline at end of file
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
new file mode 100644
index 0000000..9d3f171
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
@@ -0,0 +1,1375 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.jvm.jvmField
+import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
+import java.util.concurrent.TimeUnit
+import kotlin.test.Test
+
+class KotlinPoetTest {
+  private val tacosPackage = "com.squareup.tacos"
+
+  @Test fun topLevelMembersRetainOrder() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
+      .addType(TypeSpec.classBuilder("B").build())
+      .addProperty(
+        PropertySpec.builder("c", String::class, KModifier.PUBLIC)
+          .initializer("%S", "C")
+          .build(),
+      )
+      .addFunction(FunSpec.builder("d").build())
+      .addType(TypeSpec.classBuilder("E").build())
+      .addProperty(
+        PropertySpec.builder("f", String::class, KModifier.PUBLIC)
+          .initializer("%S", "F")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public fun a(): Unit {
+        |}
+        |
+        |public class B
+        |
+        |public val c: String = "C"
+        |
+        |public fun d(): Unit {
+        |}
+        |
+        |public class E
+        |
+        |public val f: String = "F"
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noTopLevelConstructor() {
+    assertThrows<IllegalArgumentException> {
+      FileSpec.builder(tacosPackage, "Taco")
+        .addFunction(FunSpec.constructorBuilder().build())
+    }
+  }
+
+  @Test fun primaryConstructor() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("cheese", String::class)
+            .beginControlFlow("require(cheese.isNotEmpty())")
+            .addStatement("%S", "cheese cannot be empty")
+            .endControlFlow()
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco(
+        |  cheese: String,
+        |) {
+        |  init {
+        |    require(cheese.isNotEmpty()) {
+        |      "cheese cannot be empty"
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun primaryConstructorProperties() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("cheese", String::class)
+            .addParameter("cilantro", String::class)
+            .addParameter("lettuce", String::class)
+            .beginControlFlow("require(!cheese.isEmpty())")
+            .addStatement("%S", "cheese cannot be empty")
+            .endControlFlow()
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("cheese", String::class)
+            .initializer("cheese")
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("cilantro", String::class.asTypeName())
+            .mutable()
+            .initializer("cilantro")
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("lettuce", String::class)
+            .initializer("lettuce.trim()")
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("onion", Boolean::class)
+            .initializer("true")
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Boolean
+        |import kotlin.String
+        |
+        |public class Taco(
+        |  public val cheese: String,
+        |  public var cilantro: String,
+        |  lettuce: String,
+        |) {
+        |  public val lettuce: String = lettuce.trim()
+        |
+        |  public val onion: Boolean = true
+        |  init {
+        |    require(!cheese.isEmpty()) {
+        |      "cheese cannot be empty"
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun propertyModifiers() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addProperty(
+          PropertySpec.builder("CHEESE", String::class, KModifier.PRIVATE, KModifier.CONST)
+            .initializer("%S", "monterey jack")
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("sauce", String::class.asTypeName(), KModifier.PUBLIC)
+            .mutable()
+            .initializer("%S", "chipotle mayo")
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  private const val CHEESE: String = "monterey jack"
+        |
+        |  public var sauce: String = "chipotle mayo"
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun mistargetedModifier() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("CHEESE", String::class, KModifier.DATA).build()
+    }
+  }
+
+  @Test fun visibilityModifiers() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
+        .addFunction(FunSpec.builder("b").addModifiers(KModifier.PROTECTED).build())
+        .addFunction(FunSpec.builder("c").addModifiers(KModifier.INTERNAL).build())
+        .addFunction(FunSpec.builder("d").addModifiers(KModifier.PRIVATE).build())
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun a(): Unit {
+        |  }
+        |
+        |  protected fun b(): Unit {
+        |  }
+        |
+        |  internal fun c(): Unit {
+        |  }
+        |
+        |  private fun d(): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun strings() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addFunction(
+          FunSpec.builder("strings")
+            .addStatement("val a = %S", "basic string")
+            .addStatement("val b = %S", "string with a \$ dollar sign")
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      "" +
+        "package com.squareup.tacos\n" +
+        "\n" +
+        "import kotlin.Unit\n" +
+        "\n" +
+        "public class Taco {\n" +
+        "  public fun strings(): Unit {\n" +
+        "    val a = \"basic string\"\n" +
+        "    val b = \"string with a \${\'\$\'} dollar sign\"\n" +
+        "  }\n" +
+        "}\n",
+    )
+  }
+
+  /** When emitting a triple quote, KotlinPoet escapes the 3rd quote in the triplet. */
+  @Test fun rawStrings() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addFunction(
+          FunSpec.builder("strings")
+            .addStatement("val a = %S", "\"\n")
+            .addStatement("val b = %S", "a\"\"\"b\"\"\"\"\"\"c\n")
+            .addStatement(
+              "val c = %S",
+              """
+            |whoa
+            |"raw"
+            |string
+              """.trimMargin(),
+            )
+            .addStatement(
+              "val d = %S",
+              """
+            |"raw"
+            |string
+            |with
+            |${'$'}a interpolated value
+              """.trimMargin(),
+            )
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      "" +
+        "package com.squareup.tacos\n" +
+        "\n" +
+        "import kotlin.Unit\n" +
+        "\n" +
+        "public class Taco {\n" +
+        "  public fun strings(): Unit {\n" +
+        "    val a = \"\"\"\n" +
+        "        |\"\n" +
+        "        |\"\"\".trimMargin()\n" +
+        "    val b = \"\"\"\n" +
+        "        |a\"\"\${'\"'}b\"\"\${'\"'}\"\"\${'\"'}c\n" +
+        "        |\"\"\".trimMargin()\n" +
+        "    val c = \"\"\"\n" +
+        "        |whoa\n" +
+        "        |\"raw\"\n" +
+        "        |string\n" +
+        "        \"\"\".trimMargin()\n" +
+        "    val d = \"\"\"\n" +
+        "        |\"raw\"\n" +
+        "        |string\n" +
+        "        |with\n" +
+        "        |\${\'\$\'}a interpolated value\n" +
+        "        \"\"\".trimMargin()\n" +
+        "  }\n" +
+        "}\n",
+    )
+  }
+
+  /**
+   * When a string literal ends in a newline, there's a pipe `|` immediately preceding the closing
+   * triple quote. Otherwise the closing triple quote has no preceding `|`.
+   */
+  @Test fun edgeCaseStrings() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addFunction(
+          FunSpec.builder("strings")
+            .addStatement("val a = %S", "\n")
+            .addStatement("val b = %S", " \n ")
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      "" +
+        "package com.squareup.tacos\n" +
+        "\n" +
+        "import kotlin.Unit\n" +
+        "\n" +
+        "public class Taco {\n" +
+        "  public fun strings(): Unit {\n" +
+        "    val a = \"\"\"\n" +
+        "        |\n" +
+        "        |\"\"\".trimMargin()\n" +
+        "    val b = \"\"\"\n" +
+        "        | \n" +
+        "        | \n" +
+        "        \"\"\".trimMargin()\n" +
+        "  }\n" +
+        "}\n",
+    )
+  }
+
+  @Test fun parameterDefaultValue() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("Taco")
+        .addFunction(
+          FunSpec.builder("addCheese")
+            .addParameter(
+              ParameterSpec.builder("kind", String::class)
+                .defaultValue("%S", "monterey jack")
+                .build(),
+            )
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun addCheese(kind: String = "monterey jack"): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionFunction() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("shrink")
+          .returns(String::class)
+          .receiver(String::class)
+          .addStatement("return substring(0, length - 1)")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public fun String.shrink(): String = substring(0, length - 1)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionFunctionLambda() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("shrink")
+          .returns(String::class)
+          .receiver(
+            LambdaTypeName.get(
+              parameters = arrayOf(String::class.asClassName()),
+              returnType = String::class.asTypeName(),
+            ),
+          )
+          .addStatement("return substring(0, length - 1)")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public fun ((String) -> String).shrink(): String = substring(0, length - 1)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionFunctionLambdaWithParamName() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("whatever")
+          .returns(Unit::class)
+          .receiver(
+            LambdaTypeName.get(
+              parameters = arrayOf(ParameterSpec.builder("name", String::class).build()),
+              returnType = Unit::class.asClassName(),
+            ),
+          )
+          .addStatement("return Unit")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |public fun ((name: String) -> Unit).whatever(): Unit = Unit
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionFunctionLambdaWithMultipleParams() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("whatever")
+          .returns(Unit::class)
+          .receiver(
+            LambdaTypeName.get(
+              parameters = listOf(
+                ParameterSpec.builder("name", String::class).build(),
+                ParameterSpec.unnamed(Int::class),
+                ParameterSpec.builder("age", Long::class).build(),
+              ),
+              returnType = Unit::class.asClassName(),
+            ),
+          )
+          .addStatement("return Unit")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.Long
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |public fun ((
+      |  name: String,
+      |  Int,
+      |  age: Long,
+      |) -> Unit).whatever(): Unit = Unit
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionProperty() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addProperty(
+        PropertySpec.builder("extensionProperty", Int::class)
+          .receiver(String::class)
+          .getter(
+            FunSpec.getterBuilder()
+              .addStatement("return length")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public val String.extensionProperty: Int
+        |  get() = length
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun extensionPropertyLambda() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addProperty(
+        PropertySpec.builder("extensionProperty", Int::class)
+          .receiver(
+            LambdaTypeName.get(
+              parameters = arrayOf(String::class.asClassName()),
+              returnType = String::class.asClassName(),
+            ),
+          )
+          .getter(
+            FunSpec.getterBuilder()
+              .addStatement("return length")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public val ((String) -> String).extensionProperty: Int
+        |  get() = length
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nullableTypes() {
+    val list = (List::class.asClassName().copy(nullable = true) as ClassName)
+      .parameterizedBy(Int::class.asClassName().copy(nullable = true))
+      .copy(nullable = true)
+    assertThat(list.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
+  }
+
+  @Test fun getAndSet() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addProperty(
+        PropertySpec.builder("propertyWithCustomAccessors", Int::class.asTypeName())
+          .mutable()
+          .initializer("%L", 1)
+          .getter(
+            FunSpec.getterBuilder()
+              .addStatement("println(%S)", "getter")
+              .addStatement("return field")
+              .build(),
+          )
+          .setter(
+            FunSpec.setterBuilder()
+              .addParameter("value", Int::class)
+              .addStatement("println(%S)", "setter")
+              .addStatement("field = value")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public var propertyWithCustomAccessors: Int = 1
+        |  get() {
+        |    println("getter")
+        |    return field
+        |  }
+        |  set(`value`) {
+        |    println("setter")
+        |    field = value
+        |  }
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun propertyWithLongInitializerWrapping() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addProperty(
+        PropertySpec
+          .builder("foo", ClassName(tacosPackage, "Foo").copy(nullable = true))
+          .addModifiers(KModifier.PRIVATE)
+          .initializer("DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |private val foo: Foo? =
+      |    DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun stackedPropertyModifiers() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addType(
+        TypeSpec.classBuilder("A")
+          .addModifiers(KModifier.ABSTRACT)
+          .addProperty(
+            PropertySpec.builder("q", String::class.asTypeName())
+              .mutable()
+              .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("p", String::class)
+          .addModifiers(KModifier.CONST, KModifier.INTERNAL)
+          .initializer("%S", "a")
+          .build(),
+      )
+      .addType(
+        TypeSpec.classBuilder("B")
+          .superclass(ClassName(tacosPackage, "A"))
+          .addModifiers(KModifier.ABSTRACT)
+          .addProperty(
+            PropertySpec.builder("q", String::class.asTypeName())
+              .mutable()
+              .addModifiers(
+                KModifier.FINAL,
+                KModifier.LATEINIT,
+                KModifier.OVERRIDE,
+                KModifier.PUBLIC,
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public abstract class A {
+        |  protected abstract var q: String
+        |}
+        |
+        |internal const val p: String = "a"
+        |
+        |public abstract class B : A() {
+        |  public final override lateinit var q: String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun stackedFunModifiers() {
+    val source = FileSpec.get(
+      tacosPackage,
+      TypeSpec.classBuilder("A")
+        .addModifiers(KModifier.OPEN)
+        .addFunction(
+          FunSpec.builder("get")
+            .addModifiers(
+              KModifier.EXTERNAL,
+              KModifier.INFIX,
+              KModifier.OPEN,
+              KModifier.OPERATOR,
+              KModifier.PROTECTED,
+            )
+            .addParameter("v", String::class)
+            .returns(String::class)
+            .build(),
+        )
+        .addFunction(
+          FunSpec.builder("loop")
+            .addModifiers(KModifier.FINAL, KModifier.INLINE, KModifier.INTERNAL, KModifier.TAILREC)
+            .returns(String::class)
+            .addStatement("return %S", "a")
+            .build(),
+        )
+        .build(),
+    )
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public open class A {
+        |  protected open external infix operator fun `get`(v: String): String
+        |
+        |  internal final tailrec inline fun loop(): String = "a"
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun basicExpressionBody() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("addA")
+          .addParameter("s", String::class)
+          .returns(String::class)
+          .addStatement("return s + %S", "a")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public fun addA(s: String): String = s + "a"
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun suspendingLambdas() {
+    val barType = ClassName(tacosPackage, "Bar")
+    val suspendingLambda = LambdaTypeName
+      .get(parameters = arrayOf(ClassName(tacosPackage, "Foo")), returnType = barType)
+      .copy(suspending = true)
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addProperty(
+        PropertySpec.builder("bar", suspendingLambda)
+          .mutable()
+          .initializer("{ %T() }", barType)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("nullBar", suspendingLambda.copy(nullable = true))
+          .mutable()
+          .initializer("null")
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("foo")
+          .addParameter("bar", suspendingLambda)
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public var bar: suspend (Foo) -> Bar = { Bar() }
+      |
+      |public var nullBar: (suspend (Foo) -> Bar)? = null
+      |
+      |public fun foo(bar: suspend (Foo) -> Bar): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumAsDefaultArgument() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("timeout")
+          .addParameter("duration", Long::class)
+          .addParameter(
+            ParameterSpec.builder("timeUnit", TimeUnit::class)
+              .defaultValue("%T.%L", TimeUnit::class, TimeUnit.MILLISECONDS.name)
+              .build(),
+          )
+          .addStatement("this.timeout = timeUnit.toMillis(duration)")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.util.concurrent.TimeUnit
+      |import kotlin.Long
+      |import kotlin.Unit
+      |
+      |public fun timeout(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): Unit {
+      |  this.timeout = timeUnit.toMillis(duration)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun dynamicType() {
+    val source = FileSpec.builder(tacosPackage, "Taco")
+      .addFunction(
+        FunSpec.builder("dynamicTest")
+          .addCode(
+            CodeBlock.of(
+              "%L",
+              PropertySpec.builder("d1", DYNAMIC)
+                .initializer("%S", "Taco")
+                .build(),
+            ),
+          )
+          .addCode(
+            CodeBlock.of(
+              "%L",
+              PropertySpec.builder("d2", DYNAMIC)
+                .initializer("1f")
+                .build(),
+            ),
+          )
+          .addStatement("// dynamics are dangerous!")
+          .addStatement("println(d1 - d2)")
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun dynamicTest(): Unit {
+      |  val d1: dynamic = "Taco"
+      |  val d2: dynamic = 1f
+      |  // dynamics are dangerous!
+      |  println(d1 - d2)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun primaryConstructorParameterAnnotation() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .jvmField()
+              .initializer("foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmField
+      |
+      |public class Taco(
+      |  @JvmField
+      |  public val foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/346
+  @Test fun importTypeArgumentInParameterizedTypeName() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .addParameter(
+            "a",
+            List::class.asTypeName()
+              .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.Unit
+      |import kotlin.collections.List
+      |import kotlin.jvm.JvmSuppressWildcards
+      |
+      |public fun foo(a: List<@JvmSuppressWildcards Int>): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/462
+  @Test fun foldingPropertyWithLambdaInitializer() {
+    val param = ParameterSpec.builder("arg", ANY).build()
+    val initializer = CodeBlock.builder()
+      .beginControlFlow("{ %L ->", param)
+      .addStatement("println(\"arg=\$%N\")", param)
+      .endControlFlow()
+      .build()
+    val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+    val property = PropertySpec.builder("foo", lambdaTypeName)
+      .initializer("foo")
+      .build()
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter(
+                ParameterSpec.builder("foo", lambdaTypeName)
+                  .defaultValue(initializer)
+                  .build(),
+              )
+              .build(),
+          )
+          .addProperty(property)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.example.SomeTypeAlias
+      |
+      |public class Taco(
+      |  public val foo: SomeTypeAlias = { arg: kotlin.Any ->
+      |    println("arg=${'$'}arg")
+      |  }
+      |  ,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/483
+  @Test fun foldingPropertyWithEscapedName() {
+    val file = FileSpec.builder("com.squareup.tacos", "AlarmInfo")
+      .addType(
+        TypeSpec.classBuilder("AlarmInfo")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("when", Float::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("when", Float::class)
+              .initializer("when")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Float
+      |
+      |public class AlarmInfo(
+      |  public val `when`: Float,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/577
+  @Test fun noWrappingBetweenParamNameAndType() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("functionWithAPrettyLongNameThatWouldCauseWrapping")
+          .addParameter("parameterWithALongNameThatWouldAlsoCauseWrapping", String::class)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |public
+      |    fun functionWithAPrettyLongNameThatWouldCauseWrapping(parameterWithALongNameThatWouldAlsoCauseWrapping: String):
+      |    Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/576
+  @Test fun noWrappingBetweenValAndPropertyName() {
+    val wireField = ClassName("com.squareup.wire", "WireField")
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addModifiers(KModifier.DATA)
+          .addProperty(
+            PropertySpec.builder("name", String::class)
+              .addAnnotation(
+                AnnotationSpec.builder(wireField)
+                  .addMember("tag = %L", 1)
+                  .addMember("adapter = %S", "CustomStringAdapterWithALongNameThatCauses")
+                  .build(),
+              )
+              .initializer("name")
+              .build(),
+          )
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter(
+                ParameterSpec.builder("name", String::class)
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.wire.WireField
+      |import kotlin.String
+      |
+      |public data class Taco(
+      |  @WireField(
+      |    tag = 1,
+      |    adapter = "CustomStringAdapterWithALongNameThatCauses",
+      |  )
+      |  public val name: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/578
+  @Test fun wrappingInsideKdocKeepsKdocFormatting() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Builder")
+          .addKdoc(
+            "Builder class for Foo. Allows creating instances of Foo by initializing " +
+              "a subset of their fields, following the Builder pattern.\n",
+          )
+          .addFunction(
+            FunSpec.builder("summary_text")
+              .addKdoc(
+                "The description for the choice, e.g. \"Currently unavailable due to " +
+                  "high demand. Please try later.\" May be null.",
+              )
+              .addParameter("summary_text", String::class.asClassName().copy(nullable = true))
+              .returns(ClassName("com.squareup.tacos", "Builder"))
+              .addStatement("this.summary_text = summary_text")
+              .addStatement("return this")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |
+      |/**
+      | * Builder class for Foo. Allows creating instances of Foo by initializing a subset of their fields,
+      | * following the Builder pattern.
+      | */
+      |public class Builder {
+      |  /**
+      |   * The description for the choice, e.g. "Currently unavailable due to high demand. Please try
+      |   * later." May be null.
+      |   */
+      |  public fun summary_text(summary_text: String?): Builder {
+      |    this.summary_text = summary_text
+      |    return this
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/606
+  @Test fun typeNamesInsideTemplateStringsGetImported() {
+    val taco = ClassName("com.squareup.tacos", "Taco")
+    val file = FileSpec.builder("com.squareup.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("println(%P)", CodeBlock.of("Here's a taco: \${%T()}", taco))
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.example
+      |
+      |import com.squareup.tacos.Taco
+      |import kotlin.Unit
+      |
+      |public fun main(): Unit {
+      |  println(${'"'}""Here's a taco: ${'$'}{Taco()}""${'"'})
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/606
+  @Test fun memberNamesInsideTemplateStringsGetImported() {
+    val contentToString = MemberName("kotlin.collections", "contentToString")
+    val file = FileSpec.builder("com.squareup.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("val ints = arrayOf(1, 2, 3)")
+          .addStatement("println(%P)", CodeBlock.of("\${ints.%M()}", contentToString))
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.example
+      |
+      |import kotlin.Unit
+      |import kotlin.collections.contentToString
+      |
+      |public fun main(): Unit {
+      |  val ints = arrayOf(1, 2, 3)
+      |  println(${'"'}""${'$'}{ints.contentToString()}""${'"'})
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/701
+  @Test fun noIllegalCharacterInIdentifier() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.enumBuilder("MyEnum")
+        .addEnumConstant("with.dots") // dots are illegal, so this should fail
+        .build().toString()
+    }.hasMessageThat().isEqualTo("Can't escape identifier `with.dots` because it contains illegal characters: .")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/814
+  @Test fun percentAtTheEndOfKdoc() {
+    val paramSpec1 = ParameterSpec.builder("a", Int::class)
+      .addKdoc("Progress in %%")
+      .build()
+    val paramSpec2 = ParameterSpec.builder("b", Int::class)
+      .addKdoc("Some other parameter with %%")
+      .build()
+    val funSpec = FunSpec.builder("test")
+      .addParameters(listOf(paramSpec1, paramSpec2))
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * @param a Progress in %
+      | * @param b Some other parameter with %
+      | */
+      |public fun test(a: kotlin.Int, b: kotlin.Int): kotlin.Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1031
+  @Test fun superClassGetsFullyQualifiedOnConflict() {
+    val namespace = "test"
+
+    val kotlinExceptionName = ClassName("kotlin", "Exception")
+    val customExceptionName = ClassName(namespace, "Exception")
+    val customException = TypeSpec
+      .classBuilder("Exception")
+      .superclass(kotlinExceptionName)
+      .addFunction(
+        FunSpec
+          .builder("test")
+          .addParameter("e", customExceptionName)
+          .build(),
+      )
+      .build()
+
+    val file = FileSpec.builder(namespace, "Exception")
+      .addType(customException)
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package test
+      |
+      |import kotlin.Unit
+      |
+      |public class Exception : kotlin.Exception() {
+      |  public fun test(e: Exception): Unit {
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun allStringsAreUnderscore() {
+    val file = FileSpec.builder("com.squareup.tacos", "SourceWithUnderscores")
+      .addType(
+        TypeSpec.classBuilder("SourceWithUnderscores")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("_", Float::class)
+              .addParameter("____", Float::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("_", Float::class)
+              .initializer("_")
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("____", Float::class)
+              .initializer("____")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Float
+      |
+      |public class SourceWithUnderscores(
+      |  public val `_`: Float,
+      |  public val `____`: Float,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun generatedImportAliases() {
+    val squareTaco = ClassName("com.squareup.tacos", "Taco")
+    val blockTaco = ClassName("xyz.block.tacos", "Taco")
+    val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
+    val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
+    val file = FileSpec.builder("com.example", "Test")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("val squareTaco = %L", squareTaco.constructorReference())
+          .addStatement("val blockTaco = %L", blockTaco.constructorReference())
+          .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
+          .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.cash.util.isNullOrEmpty as utilIsNullOrEmpty
+      |import com.squareup.tacos.Taco as SquareupTacosTaco
+      |import kotlin.text.isNullOrEmpty as textIsNullOrEmpty
+      |import xyz.block.tacos.Taco as BlockTacosTaco
+      |
+      |public fun main(): Unit {
+      |  val squareTaco = ::SquareupTacosTaco
+      |  val blockTaco = ::BlockTacosTaco
+      |  val isSquareTacoNull = "Taco".textIsNullOrEmpty()
+      |  val isBlockTacoNull = "Taco".utilIsNullOrEmpty()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberImportsOverGeneratedImportAliases() {
+    val squareTaco = ClassName("com.squareup.tacos", "Taco")
+    val blockTaco = ClassName("xyz.block.tacos", "Taco")
+    val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
+    val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
+    val file = FileSpec.builder("com.example", "Test")
+      .addAliasedImport(squareTaco, "SquareTaco")
+      .addAliasedImport(blockTaco, "BlockTaco")
+      .addAliasedImport(kotlinIsNullOrEmpty, "kotlinIsNullOrEmpty")
+      .addAliasedImport(cashIsNullOrEmpty, "cashIsNullOrEmpty")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("val squareTaco = %L", squareTaco.constructorReference())
+          .addStatement("val blockTaco = %L", blockTaco.constructorReference())
+          .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
+          .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.cash.util.isNullOrEmpty as cashIsNullOrEmpty
+      |import com.squareup.tacos.Taco as SquareTaco
+      |import kotlin.text.isNullOrEmpty as kotlinIsNullOrEmpty
+      |import xyz.block.tacos.Taco as BlockTaco
+      |
+      |public fun main(): Unit {
+      |  val squareTaco = ::SquareTaco
+      |  val blockTaco = ::BlockTaco
+      |  val isSquareTacoNull = "Taco".kotlinIsNullOrEmpty()
+      |  val isBlockTacoNull = "Taco".cashIsNullOrEmpty()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt
new file mode 100644
index 0000000..5b72b79
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.VARARG
+import javax.annotation.Nullable
+import kotlin.test.Test
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class LambdaTypeNameTest {
+
+  @Retention(AnnotationRetention.RUNTIME)
+  annotation class HasSomeAnnotation
+
+  @HasSomeAnnotation
+  inner class IsAnnotated
+
+  @Test fun receiverWithoutAnnotationHasNoParens() {
+    val typeName = LambdaTypeName.get(
+      receiver = Int::class.asClassName(),
+      parameters = listOf(),
+      returnType = Unit::class.asTypeName(),
+    )
+    assertThat(typeName.toString()).isEqualTo("kotlin.Int.() -> kotlin.Unit")
+  }
+
+  @Test fun receiverWithAnnotationHasParens() {
+    val annotation = IsAnnotated::class.java.getAnnotation(HasSomeAnnotation::class.java)
+    val typeName = LambdaTypeName.get(
+      receiver = Int::class.asClassName().copy(
+        annotations = listOf(AnnotationSpec.get(annotation, includeDefaultValues = true)),
+      ),
+      parameters = listOf(),
+      returnType = Unit::class.asTypeName(),
+    )
+    assertThat(typeName.toString()).isEqualTo(
+      "(@com.squareup.kotlinpoet.LambdaTypeNameTest.HasSomeAnnotation kotlin.Int).() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun contextReceiver() {
+    val typeName = LambdaTypeName.get(
+      receiver = Int::class.asTypeName(),
+      parameters = listOf(),
+      returnType = Unit::class.asTypeName(),
+      contextReceivers = listOf(STRING),
+    )
+    assertThat(typeName.toString()).isEqualTo(
+      "context(kotlin.String) kotlin.Int.() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun nullableFunctionWithContextReceiver() {
+    val typeName = LambdaTypeName.get(
+      receiver = Int::class.asTypeName(),
+      parameters = listOf(),
+      returnType = Unit::class.asTypeName(),
+      contextReceivers = listOf(STRING),
+    ).copy(nullable = true)
+    assertThat(typeName.toString()).isEqualTo(
+      "(context(kotlin.String) kotlin.Int.() -> kotlin.Unit)?",
+    )
+  }
+
+  @Test fun suspendingFunctionWithContextReceiver() {
+    val typeName = LambdaTypeName.get(
+      receiver = Int::class.asTypeName(),
+      parameters = listOf(),
+      returnType = Unit::class.asTypeName(),
+      contextReceivers = listOf(STRING),
+    ).copy(suspending = true)
+    assertThat(typeName.toString()).isEqualTo(
+      "suspend context(kotlin.String) kotlin.Int.() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun functionWithMultipleContextReceivers() {
+    val typeName = LambdaTypeName.get(
+      Int::class.asTypeName(),
+      listOf(),
+      Unit::class.asTypeName(),
+      listOf(STRING, BOOLEAN),
+    )
+    assertThat(typeName.toString()).isEqualTo(
+      "context(kotlin.String, kotlin.Boolean) kotlin.Int.() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun functionWithGenericContextReceiver() {
+    val genericType = TypeVariableName("T")
+    val typeName = LambdaTypeName.get(
+      Int::class.asTypeName(),
+      listOf(),
+      Unit::class.asTypeName(),
+      listOf(genericType),
+    )
+
+    assertThat(typeName.toString()).isEqualTo(
+      "context(T) kotlin.Int.() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun functionWithAnnotatedContextReceiver() {
+    val annotatedType = STRING.copy(annotations = listOf(AnnotationSpec.get(FunSpecTest.TestAnnotation())))
+    val typeName = LambdaTypeName.get(
+      Int::class.asTypeName(),
+      listOf(),
+      Unit::class.asTypeName(),
+      listOf(annotatedType),
+    )
+
+    assertThat(typeName.toString()).isEqualTo(
+      "context(@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation kotlin.String) kotlin.Int.() -> kotlin.Unit",
+    )
+  }
+
+  @Test fun paramsWithAnnotationsForbidden() {
+    assertThrows<IllegalArgumentException> {
+      LambdaTypeName.get(
+        parameters = arrayOf(
+          ParameterSpec.builder("foo", Int::class)
+            .addAnnotation(Nullable::class)
+            .build(),
+        ),
+        returnType = Unit::class.asTypeName(),
+      )
+    }.hasMessageThat().isEqualTo("Parameters with annotations are not allowed")
+  }
+
+  @Test fun paramsWithModifiersForbidden() {
+    assertThrows<IllegalArgumentException> {
+      LambdaTypeName.get(
+        parameters = arrayOf(
+          ParameterSpec.builder("foo", Int::class)
+            .addModifiers(VARARG)
+            .build(),
+        ),
+        returnType = Unit::class.asTypeName(),
+      )
+    }.hasMessageThat().isEqualTo("Parameters with modifiers are not allowed")
+  }
+
+  @Test fun paramsWithDefaultValueForbidden() {
+    assertThrows<IllegalArgumentException> {
+      LambdaTypeName.get(
+        parameters = arrayOf(
+          ParameterSpec.builder("foo", Int::class)
+            .defaultValue("42")
+            .build(),
+        ),
+        returnType = Unit::class.asTypeName(),
+      )
+    }.hasMessageThat().isEqualTo("Parameters with default values are not allowed")
+  }
+
+  @Test fun lambdaReturnType() {
+    val returnTypeName = LambdaTypeName.get(
+      parameters = arrayOf(Int::class.asTypeName()),
+      returnType = Unit::class.asTypeName(),
+    )
+    val typeName = LambdaTypeName.get(
+      parameters = arrayOf(Int::class.asTypeName()),
+      returnType = returnTypeName,
+    )
+    assertThat(typeName.toString())
+      .isEqualTo("(kotlin.Int) -> ((kotlin.Int) -> kotlin.Unit)")
+  }
+
+  @Test fun lambdaParameterType() {
+    val parameterTypeName = LambdaTypeName.get(
+      parameters = arrayOf(Int::class.asTypeName()),
+      returnType = Int::class.asTypeName(),
+    )
+    val typeName = LambdaTypeName.get(
+      parameters = arrayOf(parameterTypeName),
+      returnType = Unit::class.asTypeName(),
+    )
+    assertThat(typeName.toString())
+      .isEqualTo("((kotlin.Int) -> kotlin.Int) -> kotlin.Unit")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt
new file mode 100644
index 0000000..8fdbda8
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class LineWrapperTest {
+  @Test fun wrap() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghij", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcde
+        |    fghij
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noWrap() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghi", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo("abcde fghi")
+  }
+
+  @Test fun multipleWrite() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("ab cd ef gh ij kl mn op qr", indentLevel = 1)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |ab cd ef
+        |  gh ij kl
+        |  mn op qr
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun fencepost() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde", indentLevel = 2)
+    lineWrapper.append("fghij k", indentLevel = 2)
+    lineWrapper.append("lmnop", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcdefghij
+        |    klmnop
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun overlyLongLinesWithoutLeadingSpace() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcdefghijkl", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo("abcdefghijkl")
+  }
+
+  @Test fun overlyLongLinesWithLeadingSpace() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append(" abcdefghijkl", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo("\n    abcdefghijkl")
+  }
+
+  @Test fun noWrapEmbeddedNewlines() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghi\njklmn", indentLevel = 2)
+    lineWrapper.append("opqrstuvwxy", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcde fghi
+        |jklmnopqrstuvwxy
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun wrapEmbeddedNewlines() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghij\nklmn", indentLevel = 2)
+    lineWrapper.append("opqrstuvwxy", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcde
+        |    fghij
+        |klmnopqrstuvwxy
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noWrapMultipleNewlines() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghi\nklmnopq\nr stuvwxyz", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcde fghi
+        |klmnopq
+        |r stuvwxyz
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun wrapMultipleNewlines() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("abcde fghi\nklmnopq\nrs tuvwxyz1", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |abcde fghi
+        |klmnopq
+        |rs
+        |    tuvwxyz1
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noWrapPrecedingUnaryPlus() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("a + b       + c", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |a +
+        |    b       +
+        |    c
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noWrapPrecedingUnaryMinus() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("a - b       - c", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |a -
+        |    b       -
+        |    c
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun appendNonWrapping() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("ab cd ef", indentLevel = 2)
+    lineWrapper.appendNonWrapping("gh ij kl mn")
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |ab cd
+        |    efgh ij kl mn
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun appendNonWrappingSpace() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("ab cd ef", indentLevel = 2)
+    lineWrapper.append("gh·ij·kl·mn", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |ab cd
+        |    efgh ij kl mn
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun loneUnsafeUnaryOperator() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append(" -1", indentLevel = 2)
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        | -1
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun linePrefix() {
+    val out = StringBuffer()
+    val lineWrapper = LineWrapper(out, "  ", 10)
+    lineWrapper.append("/**\n")
+    lineWrapper.append(" * ")
+    lineWrapper.append("a b c d e f g h i j k l m n\n", linePrefix = " * ")
+    lineWrapper.append(" */")
+    lineWrapper.close()
+    assertThat(out.toString()).isEqualTo(
+      """
+        |/**
+        | * a b c d
+        | * e f g h i j
+        | * k l m n
+        | */
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt
new file mode 100644
index 0000000..332a25b
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class LineWrappingTest {
+  @Test fun codeSpacesWrap() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement(
+        "return %L * %L * %L * %L * %L * %L * %L * %L * %L * %L * %L * %L",
+        10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000,
+        70000000000, 80000000000, 90000000000, 10000000000, 20000000000, 30000000000,
+      )
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public fun wrapMe() = 10_000_000_000 * 20_000_000_000 * 30_000_000_000 * 40_000_000_000 *
+        |    50_000_000_000 * 60_000_000_000 * 70_000_000_000 * 80_000_000_000 * 90_000_000_000 *
+        |    10_000_000_000 * 20_000_000_000 * 30_000_000_000
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun stringSpacesDoNotWrap() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement(
+        "return %S+%S+%S+%S+%S+%S+%S+%S+%S+%S+%S+%S",
+        "Aaaa Aaaa", "Bbbb Bbbb", "Cccc Cccc", "Dddd Dddd", "Eeee Eeee", "Ffff Ffff",
+        "Gggg Gggg", "Hhhh Hhhh", "Iiii Iiii", "Jjjj Jjjj", "Kkkk Kkkk", "Llll Llll",
+      )
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public fun wrapMe() =
+        |    "Aaaa Aaaa"+"Bbbb Bbbb"+"Cccc Cccc"+"Dddd Dddd"+"Eeee Eeee"+"Ffff Ffff"+"Gggg Gggg"+"Hhhh Hhhh"+"Iiii Iiii"+"Jjjj Jjjj"+"Kkkk Kkkk"+"Llll Llll"
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nonwrappingWhitespaceDoesNotWrap() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement(
+        "return %L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L",
+        10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000,
+        70000000000, 80000000000, 90000000000, 10000000000, 20000000000, 30000000000,
+      )
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public fun wrapMe() =
+        |    10_000_000_000 * 20_000_000_000 * 30_000_000_000 * 40_000_000_000 * 50_000_000_000 * 60_000_000_000 * 70_000_000_000 * 80_000_000_000 * 90_000_000_000 * 10_000_000_000 * 20_000_000_000 * 30_000_000_000
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nonwrappingWhitespaceIsRetainedInStrings() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement("return %S", "a·b")
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public fun wrapMe() = "a·b"
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun insignificantWhitespaceRetained() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement("val a =    8")
+      .addStatement("val b =   64")
+      .addStatement("val c =  512")
+      .addStatement("val d = 4096")
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public fun wrapMe(): Unit {
+        |  val a =    8
+        |  val b =   64
+        |  val c =  512
+        |  val d = 4096
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun spacesPrecedingUnaryOperatorsDoNotWrap() {
+    val wrapMe = FunSpec.builder("wrapMe")
+      .addStatement("val aaaaaa = %S +1", "x".repeat(80))
+      .addStatement("val bbbbbb = %S +1", "x".repeat(81))
+      .addStatement("val cccccc = %S -1", "x".repeat(80))
+      .addStatement("val dddddd = %S -1", "x".repeat(81))
+      .build()
+    assertThat(toString(wrapMe)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public fun wrapMe(): Unit {
+        |  val aaaaaa = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +1
+        |  val bbbbbb =
+        |      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +1
+        |  val cccccc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -1
+        |  val dddddd =
+        |      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -1
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun parameterWrapping() {
+    val funSpecBuilder = FunSpec.builder("call")
+    funSpecBuilder.addCode("«call(")
+    for (i in 0..31) {
+      funSpecBuilder.addParameter("s$i", String::class)
+      funSpecBuilder.addCode(if (i > 0) ", %S" else "%S", i)
+    }
+    funSpecBuilder.addCode(")»\n")
+
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(funSpecBuilder.build())
+      .build()
+    assertThat(toString(taco))
+      .isEqualTo(
+        """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun call(
+        |    s0: String,
+        |    s1: String,
+        |    s2: String,
+        |    s3: String,
+        |    s4: String,
+        |    s5: String,
+        |    s6: String,
+        |    s7: String,
+        |    s8: String,
+        |    s9: String,
+        |    s10: String,
+        |    s11: String,
+        |    s12: String,
+        |    s13: String,
+        |    s14: String,
+        |    s15: String,
+        |    s16: String,
+        |    s17: String,
+        |    s18: String,
+        |    s19: String,
+        |    s20: String,
+        |    s21: String,
+        |    s22: String,
+        |    s23: String,
+        |    s24: String,
+        |    s25: String,
+        |    s26: String,
+        |    s27: String,
+        |    s28: String,
+        |    s29: String,
+        |    s30: String,
+        |    s31: String,
+        |  ): Unit {
+        |    call("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16",
+        |        "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31")
+        |  }
+        |}
+        |
+        """.trimMargin(),
+      )
+  }
+
+  private fun toString(typeSpec: TypeSpec): String {
+    return FileSpec.get("com.squareup.tacos", typeSpec).toString()
+  }
+
+  private fun toString(funSpec: FunSpec): String {
+    val fileSpec = FileSpec.builder("com.squareup.tacos", "${funSpec.name}.kt")
+      .addFunction(funSpec)
+      .build()
+    return fileSpec.toString()
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
new file mode 100644
index 0000000..308aeb2
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.OVERRIDE
+import com.squareup.kotlinpoet.MemberName.Companion.member
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import org.junit.Before
+import org.junit.Test
+
+class MemberNameTest {
+  @Test fun memberNames() {
+    val randomTaco = MemberName("com.squareup.tacos", "randomTaco")
+    val bestTacoEver = MemberName("com.squareup.tacos", "bestTacoEver")
+    val funSpec = FunSpec.builder("makeTastyTacos")
+      .addStatement("val randomTaco = %M()", randomTaco)
+      .addStatement("val bestTaco = %M", bestTacoEver)
+      .build()
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(funSpec)
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.bestTacoEver
+      |import com.squareup.tacos.randomTaco
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  val randomTaco = randomTaco()
+      |  val bestTaco = bestTacoEver
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberInsideCompanionObject() {
+    val companion = ClassName("com.squareup.tacos", "Taco").nestedClass("Companion")
+    val createTaco = MemberName(companion, "createTaco")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.Taco.Companion.createTaco
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  createTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberInsideSamePackage() {
+    val createTaco = MemberName("com.squareup.tacos", "createTaco")
+    val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  createTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberInsideClassInSamePackage() {
+    val createTaco = MemberName(
+      ClassName("com.squareup.tacos", "Town"),
+      "createTaco",
+    )
+    val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.tacos.Town.createTaco
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  createTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberNamesClash() {
+    val createSquareTaco = MemberName("com.squareup.tacos", "createTaco")
+    val createTwitterTaco = MemberName("com.twitter.tacos", "createTaco")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createSquareTaco)
+          .addStatement("%M()", createTwitterTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.tacos.createTaco as squareupTacosCreateTaco
+      |import com.twitter.tacos.createTaco as twitterTacosCreateTaco
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  squareupTacosCreateTaco()
+      |  twitterTacosCreateTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberNamesInsideCompanionsClash() {
+    val squareTacos = ClassName("com.squareup.tacos", "SquareTacos")
+    val twitterTacos = ClassName("com.twitter.tacos", "TwitterTacos")
+    val createSquareTaco = MemberName(squareTacos.nestedClass("Companion"), "createTaco")
+    val createTwitterTaco = MemberName(twitterTacos.nestedClass("Companion"), "createTaco")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createSquareTaco)
+          .addStatement("%M()", createTwitterTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareupTacosCreateTaco
+      |import com.twitter.tacos.TwitterTacos.Companion.createTaco as twitterTacosCreateTaco
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  squareupTacosCreateTaco()
+      |  twitterTacosCreateTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberAndClassNamesClash() {
+    val squareTacosClass = ClassName("com.squareup.tacos", "SquareTacos")
+    val squareTacosFunction = MemberName("com.squareup.tacos.math", "SquareTacos")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("val tacos = %T()", squareTacosClass)
+          .addStatement("%M(tacos)", squareTacosFunction)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.SquareTacos
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  val tacos = SquareTacos()
+      |  com.squareup.tacos.math.SquareTacos(tacos)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importedMemberAndClassFunctionNameClash() {
+    val kotlinErrorMember = MemberName("kotlin", "error")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addType(
+        TypeSpec.classBuilder("TacoTest")
+          .addFunction(
+            FunSpec.builder("test")
+              .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+              .build(),
+          )
+          .addFunction(
+            FunSpec
+              .builder("error")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public class TacoTest {
+      |  public fun test(): Unit {
+      |    kotlin.error("errorText")
+      |  }
+      |
+      |  public fun error(): Unit {
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importedMemberAndSuperClassFunctionNameClashForInnerClass() {
+    val kotlinErrorMember = MemberName("kotlin", "error")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addType(
+        TypeSpec.classBuilder("Test")
+          .addFunction(
+            FunSpec
+              .builder("error")
+              .build(),
+          )
+          .addType(
+            TypeSpec.classBuilder("TacoTest")
+              .addModifiers(KModifier.INNER)
+              .addFunction(
+                FunSpec.builder("test")
+                  .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public class Test {
+      |  public fun error(): Unit {
+      |  }
+      |
+      |  public inner class TacoTest {
+      |    public fun test(): Unit {
+      |      kotlin.error("errorText")
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importedMemberAndSuperClassFunctionNameDontClashForNonInnerClass() {
+    val kotlinErrorMember = MemberName("kotlin", "error")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addType(
+        TypeSpec.classBuilder("Test")
+          .addFunction(
+            FunSpec
+              .builder("error")
+              .build(),
+          )
+          .addType(
+            TypeSpec.classBuilder("TacoTest")
+              .addFunction(
+                FunSpec.builder("test")
+                  .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import kotlin.error
+      |
+      |public class Test {
+      |  public fun error(): Unit {
+      |  }
+      |
+      |  public class TacoTest {
+      |    public fun test(): Unit {
+      |      error("errorText")
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberNameAliases() {
+    val createSquareTaco = MemberName("com.squareup.tacos", "createTaco")
+    val createTwitterTaco = MemberName("com.twitter.tacos", "createTaco")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addAliasedImport(createSquareTaco, "createSquareTaco")
+      .addAliasedImport(createTwitterTaco, "createTwitterTaco")
+      .addFunction(
+        FunSpec.builder("makeTastyTacos")
+          .addStatement("%M()", createSquareTaco)
+          .addStatement("%M()", createTwitterTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.tacos.createTaco as createSquareTaco
+      |import com.twitter.tacos.createTaco as createTwitterTaco
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  createSquareTaco()
+      |  createTwitterTaco()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun keywordsEscaping() {
+    val `when` = MemberName("org.mockito", "when")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addType(
+        TypeSpec.classBuilder("TacoTest")
+          .addFunction(
+            FunSpec.builder("setUp")
+              .addAnnotation(Before::class)
+              .addStatement("%M(tacoService.createTaco()).thenReturn(tastyTaco())", `when`)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import org.junit.Before
+      |import org.mockito.`when`
+      |
+      |public class TacoTest {
+      |  @Before
+      |  public fun setUp(): Unit {
+      |    `when`(tacoService.createTaco()).thenReturn(tastyTaco())
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun clashingNamesKeywordsEscaping() {
+    val squareTacos = ClassName("com.squareup.tacos", "SquareTacos")
+    val twitterTacos = ClassName("com.twitter.tacos", "TwitterTacos")
+    val whenSquareTaco = MemberName(squareTacos.nestedClass("Companion"), "when")
+    val whenTwitterTaco = MemberName(twitterTacos.nestedClass("Companion"), "when")
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(
+        FunSpec.builder("whenTastyTacos")
+          .addStatement("%M()", whenSquareTaco)
+          .addStatement("%M()", whenTwitterTaco)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import com.squareup.tacos.SquareTacos.Companion.`when` as squareupTacosWhen
+      |import com.twitter.tacos.TwitterTacos.Companion.`when` as twitterTacosWhen
+      |
+      |public fun whenTastyTacos(): Unit {
+      |  squareupTacosWhen()
+      |  twitterTacosWhen()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberReferences() {
+    val randomTaco = MemberName("com.squareup.tacos", "randomTaco")
+    val bestTacoEver = ClassName("com.squareup.tacos", "TacoTruck")
+      .member("bestTacoEver")
+    val funSpec = FunSpec.builder("makeTastyTacos")
+      .addStatement("val randomTacoFactory = %L", randomTaco.reference())
+      .addStatement("val bestTacoFactory = %L", bestTacoEver.reference())
+      .build()
+    val file = FileSpec.builder("com.example", "Tacos")
+      .addFunction(funSpec)
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.TacoTruck
+      |import com.squareup.tacos.randomTaco
+      |import kotlin.Unit
+      |
+      |public fun makeTastyTacos(): Unit {
+      |  val randomTacoFactory = ::randomTaco
+      |  val bestTacoFactory = TacoTruck::bestTacoEver
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun spacesEscaping() {
+    val produceTacos = MemberName("com.squareup.taco factory", "produce tacos")
+    val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("println(%M())", produceTacos)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.`taco factory`.`produce tacos`
+      |import kotlin.Unit
+      |
+      |public fun main(): Unit {
+      |  println(`produce tacos`())
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun memberExtension_className() {
+    val regex = ClassName("kotlin.text", "Regex")
+    assertThat(regex.member("fromLiteral"))
+      .isEqualTo(MemberName(regex, "fromLiteral"))
+  }
+
+  @Test fun memberExtension_kclass() {
+    assertThat(Regex::class.member("fromLiteral"))
+      .isEqualTo(MemberName(ClassName("kotlin.text", "Regex"), "fromLiteral"))
+  }
+
+  @Test fun memberExtension_class() {
+    assertThat(Regex::class.java.member("fromLiteral"))
+      .isEqualTo(MemberName(ClassName("kotlin.text", "Regex"), "fromLiteral"))
+  }
+
+  @Test fun `%N escapes MemberNames`() {
+    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()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.Taco
+      |import com.squareup.tacos.TacoPackager
+      |import kotlin.Unit
+      |import kotlin.collections.List
+      |
+      |public fun packageTacos(tacos: List<Taco>, packager: TacoPackager): Unit {
+      |  packager.`package`(tacos)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importOperator() {
+    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()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import com.squareup.tacos.Taco
+      |import com.squareup.tacos.`internal`.iterator
+      |import com.squareup.tacos.`internal`.minusAssign
+      |import com.squareup.tacos.ingredient.Meat
+      |import kotlin.Unit
+      |
+      |public fun makeTacoHealthy(taco: Taco): Unit {
+      |  for (ingredient in taco) {
+      |    if (ingredient is Meat) taco -= ingredient
+      |  }
+      |  return taco
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1089
+  @Test fun `extension MemberName imported if name clash`() {
+    val hashCode = MemberName("kotlin", "hashCode", isExtension = true)
+    val file = FileSpec.builder("com.squareup.tacos", "Message")
+      .addType(
+        TypeSpec.classBuilder("Message")
+          .addFunction(
+            FunSpec.builder("hashCode")
+              .addModifiers(OVERRIDE)
+              .returns(INT)
+              .addCode(
+                buildCodeBlock {
+                  addStatement("var result = super.hashCode")
+                  beginControlFlow("if (result == 0)")
+                  addStatement("result = result * 37 + embedded_message.%M()", hashCode)
+                  addStatement("super.hashCode = result")
+                  endControlFlow()
+                  addStatement("return result")
+                },
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    //language=kotlin
+    assertThat(file.toString()).isEqualTo(
+      """
+      package com.squareup.tacos
+
+      import kotlin.Int
+      import kotlin.hashCode
+
+      public class Message {
+        public override fun hashCode(): Int {
+          var result = super.hashCode
+          if (result == 0) {
+            result = result * 37 + embedded_message.hashCode()
+            super.hashCode = result
+          }
+          return result
+        }
+      }
+
+      """.trimIndent(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt
new file mode 100644
index 0000000..28c7d34
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class NameAllocatorTest {
+  @Test fun usage() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo")
+    assertThat(nameAllocator.newName("bar", 2)).isEqualTo("bar")
+    assertThat(nameAllocator[1]).isEqualTo("foo")
+    assertThat(nameAllocator[2]).isEqualTo("bar")
+  }
+
+  @Test fun nameCollision() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo")
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo_")
+    assertThat(nameAllocator.newName("foo")).isEqualTo("foo__")
+  }
+
+  @Test fun nameCollisionWithTag() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo")
+    assertThat(nameAllocator.newName("foo", 2)).isEqualTo("foo_")
+    assertThat(nameAllocator.newName("foo", 3)).isEqualTo("foo__")
+    assertThat(nameAllocator[1]).isEqualTo("foo")
+    assertThat(nameAllocator[2]).isEqualTo("foo_")
+    assertThat(nameAllocator[3]).isEqualTo("foo__")
+  }
+
+  @Test fun characterMappingSubstitute() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("a-b", 1)).isEqualTo("a_b")
+  }
+
+  @Test fun characterMappingSurrogate() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("a\uD83C\uDF7Ab", 1)).isEqualTo("a_b")
+  }
+
+  @Test fun characterMappingInvalidStartButValidPart() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab")
+  }
+
+  @Test fun characterMappingInvalidStartIsInvalidPart() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("&ab", 1)).isEqualTo("_ab")
+  }
+
+  @Test fun kotlinKeyword() {
+    val nameAllocator = NameAllocator()
+    assertThat(nameAllocator.newName("when", 1)).isEqualTo("when_")
+    assertThat(nameAllocator[1]).isEqualTo("when_")
+  }
+
+  @Test fun tagReuseForbidden() {
+    val nameAllocator = NameAllocator()
+    nameAllocator.newName("foo", 1)
+    assertThrows<IllegalArgumentException> {
+      nameAllocator.newName("bar", 1)
+    }.hasMessageThat().isEqualTo("tag 1 cannot be used for both 'foo' and 'bar'")
+  }
+
+  @Test fun useBeforeAllocateForbidden() {
+    val nameAllocator = NameAllocator()
+    assertThrows<IllegalArgumentException> {
+      nameAllocator[1]
+    }.hasMessageThat().isEqualTo("unknown tag: 1")
+  }
+
+  @Test fun cloneUsage() {
+    val outerAllocator = NameAllocator()
+    outerAllocator.newName("foo", 1)
+
+    val innerAllocator1 = outerAllocator.copy()
+    assertThat(innerAllocator1.newName("bar", 2)).isEqualTo("bar")
+    assertThat(innerAllocator1.newName("foo", 3)).isEqualTo("foo_")
+
+    val innerAllocator2 = outerAllocator.copy()
+    assertThat(innerAllocator2.newName("foo", 2)).isEqualTo("foo_")
+    assertThat(innerAllocator2.newName("bar", 3)).isEqualTo("bar")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt
new file mode 100644
index 0000000..d764f44
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import javax.lang.model.element.Modifier
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class ParameterSpecTest {
+  @Test fun equalsAndHashCode() {
+    var a = ParameterSpec.builder("foo", Int::class)
+      .build()
+    var b = ParameterSpec.builder("foo", Int::class)
+      .build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = ParameterSpec.builder("i", Int::class)
+      .addModifiers(KModifier.NOINLINE)
+      .build()
+    b = ParameterSpec.builder("i", Int::class)
+      .addModifiers(KModifier.NOINLINE)
+      .build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun escapeKeywordInParameterName() {
+    val parameterSpec = ParameterSpec.builder("if", String::class)
+      .build()
+    assertThat(parameterSpec.toString()).isEqualTo("`if`: kotlin.String")
+  }
+
+  @Test fun escapePunctuationInParameterName() {
+    val parameterSpec = ParameterSpec.builder("with-hyphen", String::class)
+      .build()
+    assertThat(parameterSpec.toString()).isEqualTo("`with-hyphen`: kotlin.String")
+  }
+
+  @Test fun generalBuilderEqualityTest() {
+    val parameterSpec = ParameterSpec.builder("Nuts", String::class)
+      .addAnnotation(ClassName("com.squareup.kotlinpoet", "Food"))
+      .addModifiers(KModifier.VARARG)
+      .defaultValue("Almonds")
+      .build()
+
+    assertThat(parameterSpec.toBuilder().build()).isEqualTo(parameterSpec)
+  }
+
+  @Test fun modifyModifiers() {
+    val builder = ParameterSpec
+      .builder("word", String::class)
+      .addModifiers(KModifier.NOINLINE)
+
+    builder.modifiers.clear()
+    builder.modifiers.add(KModifier.CROSSINLINE)
+
+    assertThat(builder.build().modifiers).containsExactly(KModifier.CROSSINLINE)
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = ParameterSpec
+      .builder("word", String::class)
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class.asClassName())
+          .addMember("name = %S", "jvmWord")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+      .addMember("name = %S", "javaWord")
+      .build()
+    builder.annotations.clear()
+    builder.annotations.add(javaWord)
+
+    assertThat(builder.build().annotations).containsExactly(javaWord)
+  }
+
+  // https://github.com/square/kotlinpoet/issues/462
+  @Test fun codeBlockDefaultValue() {
+    val param = ParameterSpec.builder("arg", ANY).build()
+    val defaultValue = CodeBlock.builder()
+      .beginControlFlow("{ %L ->", param)
+      .addStatement("println(\"arg=\$%N\")", param)
+      .endControlFlow()
+      .build()
+    val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+    val paramSpec = ParameterSpec.builder("parameter", lambdaTypeName)
+      .defaultValue(defaultValue)
+      .build()
+    assertThat(paramSpec.toString()).isEqualTo(
+      """
+      |parameter: com.example.SomeTypeAlias = { arg: kotlin.Any ->
+      |  println("arg=${'$'}arg")
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedLambdaType() {
+    val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+    val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+    val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .addParameter("bar", type)
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public fun foo(bar: @Annotation () -> Unit): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun doublePropertyInitialization() {
+    val codeBlockDefaultValue = ParameterSpec.builder("listA", String::class)
+      .defaultValue(CodeBlock.builder().add("foo").build())
+      .defaultValue(CodeBlock.builder().add("bar").build())
+      .build()
+
+    assertThat(CodeBlock.of("bar")).isEqualTo(codeBlockDefaultValue.defaultValue)
+
+    val formatDefaultValue = ParameterSpec.builder("listA", String::class)
+      .defaultValue("foo")
+      .defaultValue("bar")
+      .build()
+
+    assertThat(CodeBlock.of("bar")).isEqualTo(formatDefaultValue.defaultValue)
+  }
+
+  @Suppress("DEPRECATION_ERROR")
+  @Test
+  fun jvmModifiersAreNotAllowed() {
+    val e = assertFailsWith<IllegalArgumentException> {
+      ParameterSpec.builder("value", INT)
+        .jvmModifiers(listOf(Modifier.FINAL))
+        .build()
+    }
+    assertThat(e).hasMessageThat().contains("JVM modifiers are not permitted on parameters in Kotlin")
+  }
+
+  @Test
+  fun illegalModifiers() {
+    val builder = ParameterSpec.builder("value", INT)
+
+    val e = assertFailsWith<IllegalArgumentException> {
+      // Legal
+      builder.addModifiers(KModifier.NOINLINE)
+      builder.addModifiers(KModifier.CROSSINLINE)
+      builder.addModifiers(KModifier.VARARG)
+      // Everything else is illegal
+      builder.addModifiers(KModifier.FINAL)
+      builder.addModifiers(KModifier.PRIVATE)
+      builder.build()
+    }
+    assertThat(e).hasMessageThat().contains("Modifiers [FINAL, PRIVATE] are not allowed on Kotlin parameters. Allowed modifiers: [VARARG, NOINLINE, CROSSINLINE]")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt
new file mode 100644
index 0000000..f4e5625
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
+import java.io.Closeable
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.KMutableProperty
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+import kotlin.reflect.full.createType
+import org.junit.Test
+
+class ParameterizedTypeNameTest {
+  @Test fun classNamePlusParameter() {
+    val typeName = ClassName("kotlin.collections", "List")
+      .plusParameter(ClassName("kotlin", "String"))
+    assertThat(typeName.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+  }
+
+  @Test fun classNamePlusTwoParameters() {
+    val typeName = ClassName("kotlin.collections", "Map")
+      .plusParameter(ClassName("kotlin", "String"))
+      .plusParameter(ClassName("kotlin", "Int"))
+    assertThat(typeName.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Int>")
+  }
+
+  @Test fun classNamePlusTypeVariableParameter() {
+    val t = TypeVariableName("T")
+    val mapOfT = Map::class.asTypeName().plusParameter(t)
+    assertThat(mapOfT.toString()).isEqualTo("kotlin.collections.Map<T>")
+  }
+
+  @Test fun kClassPlusParameter() {
+    val typeName = List::class.plusParameter(String::class)
+    assertThat(typeName.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+  }
+
+  @Test fun kClassPlusTwoParameters() {
+    val typeName = Map::class
+      .plusParameter(String::class)
+      .plusParameter(Int::class)
+    assertThat(typeName.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Int>")
+  }
+
+  @Test fun classPlusParameter() {
+    val typeName = java.util.List::class.java.plusParameter(java.lang.String::class.java)
+    assertThat(typeName.toString()).isEqualTo("java.util.List<java.lang.String>")
+  }
+
+  @Test fun primitiveArray() {
+    assertThat(ByteArray::class.asTypeName().toString()).isEqualTo("kotlin.ByteArray")
+    assertThat(CharArray::class.asTypeName().toString()).isEqualTo("kotlin.CharArray")
+    assertThat(ShortArray::class.asTypeName().toString()).isEqualTo("kotlin.ShortArray")
+    assertThat(IntArray::class.asTypeName().toString()).isEqualTo("kotlin.IntArray")
+    assertThat(LongArray::class.asTypeName().toString()).isEqualTo("kotlin.LongArray")
+    assertThat(FloatArray::class.asTypeName().toString()).isEqualTo("kotlin.FloatArray")
+    assertThat(DoubleArray::class.asTypeName().toString()).isEqualTo("kotlin.DoubleArray")
+  }
+
+  @Test fun arrayPlusPrimitiveParameter() {
+    val invariantInt = KTypeProjection(KVariance.INVARIANT, Int::class.createType())
+    val typeName = Array<Unit>::class.createType(listOf(invariantInt)).asTypeName()
+    assertThat(typeName.toString()).isEqualTo("kotlin.Array<kotlin.Int>")
+  }
+
+  @Test fun arrayPlusObjectParameter() {
+    val invariantCloseable = KTypeProjection(KVariance.INVARIANT, Closeable::class.createType())
+    val typeName = Array<Unit>::class.createType(listOf(invariantCloseable)).asTypeName()
+    assertThat(typeName.toString()).isEqualTo("kotlin.Array<java.io.Closeable>")
+  }
+
+  @Test fun arrayPlusNullableParameter() {
+    val invariantNullableCloseable = KTypeProjection(KVariance.INVARIANT, Closeable::class.createType(nullable = true))
+    val typeName = Array<Unit>::class.createType(listOf(invariantNullableCloseable)).asTypeName()
+    assertThat(typeName.toString()).isEqualTo("kotlin.Array<java.io.Closeable?>")
+  }
+
+  @Test fun typeParameter() {
+    val funWithParam: () -> Closeable = this::withParam
+    val typeName = (funWithParam as KFunction<*>).returnType.asTypeName()
+    assertThat(typeName.toString()).isEqualTo("Param")
+  }
+
+  @Test fun nullableTypeParameter() {
+    val funWithParam: () -> Closeable? = this::withNullableParam
+    val typeName = (funWithParam as KFunction<*>).returnType.asTypeName()
+    assertThat(typeName.toString()).isEqualTo("Param?")
+  }
+
+  @Test fun classPlusTwoParameters() {
+    val typeName = java.util.Map::class.java
+      .plusParameter(java.lang.String::class.java)
+      .plusParameter(java.lang.Integer::class.java)
+    assertThat(typeName.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Integer>")
+  }
+
+  @Test fun copyingTypeArguments() {
+    val typeName = java.util.Map::class.java
+      .plusParameter(java.lang.String::class.java)
+      .plusParameter(java.lang.Integer::class.java)
+      .nestedClass(
+        "Entry",
+        listOf(
+          java.lang.String::class.java.asClassName(),
+          java.lang.Integer::class.java.asClassName(),
+        ),
+      )
+      .copy(typeArguments = listOf(STAR, STAR))
+    assertThat(typeName.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Integer>.Entry<*, *>")
+  }
+
+  interface Projections {
+    val outVariance: KClass<out Annotation>
+    val inVariance: KClass<in Test>
+    val invariantNullable: KClass<Test>?
+    val star: KClass<*>
+    val multiVariant: Map<in String, List<Map<KClass<out Number>, *>?>>
+    val outAnyOnTypeWithoutBoundsAndVariance: KMutableProperty<out Any>
+  }
+
+  private fun assertKTypeProjections(kType: KType) = assertThat(kType.asTypeName().toString()).isEqualTo(kType.toString())
+
+  @Test fun kTypeOutProjection() = assertKTypeProjections(Projections::outVariance.returnType)
+
+  @Test fun kTypeInProjection() = assertKTypeProjections(Projections::inVariance.returnType)
+
+  @Test fun kTypeInvariantNullableProjection() = assertKTypeProjections(Projections::invariantNullable.returnType)
+
+  @Test fun kTypeStarProjection() = assertKTypeProjections(Projections::star.returnType)
+
+  @Test fun kTypeMultiVariantProjection() = assertKTypeProjections(Projections::multiVariant.returnType)
+
+  @Test fun kTypeOutAnyOnTypeWithoutBoundsVariance() = assertKTypeProjections(Projections::outAnyOnTypeWithoutBoundsAndVariance.returnType)
+
+  private fun <Param : Closeable> withParam(): Param = throw NotImplementedError("for testing purposes")
+
+  private fun <Param : Closeable> withNullableParam(): Param? = throw NotImplementedError("for testing purposes")
+
+  @Test fun annotatedLambdaTypeParameter() {
+    val annotation = AnnotationSpec.builder(ClassName("", "Annotation")).build()
+    val typeName = Map::class.asTypeName()
+      .plusParameter(String::class.asTypeName())
+      .plusParameter(LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation)))
+    assertThat(typeName.toString())
+      .isEqualTo("kotlin.collections.Map<kotlin.String, @Annotation () -> kotlin.Unit>")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
new file mode 100644
index 0000000..5eda0f7
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Serializable
+import java.util.function.Function
+import kotlin.reflect.KClass
+import kotlin.test.Test
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class PropertySpecTest {
+  annotation class TestAnnotation
+
+  @Test fun nullable() {
+    val type = String::class.asClassName().copy(nullable = true)
+    val a = PropertySpec.builder("foo", type).build()
+    assertThat(a.toString()).isEqualTo("val foo: kotlin.String?\n")
+  }
+
+  @Test fun delegated() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .delegate("Delegates.notNull()")
+      .build()
+    assertThat(prop.toString()).isEqualTo("val foo: kotlin.String by Delegates.notNull()\n")
+  }
+
+  @Test fun emptySetter() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .mutable()
+      .setter(
+        FunSpec.setterBuilder()
+          .addModifiers(PRIVATE)
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |var foo: kotlin.String
+      |  private set
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/952
+  @Test fun emptySetterCannotHaveBody() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("foo", String::class)
+        .mutable()
+        .setter(
+          FunSpec.setterBuilder()
+            .addStatement("body()")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("parameterless setter cannot have code")
+  }
+
+  @Test fun externalGetterAndSetter() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .mutable()
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(EXTERNAL)
+          .build(),
+      )
+      .setter(
+        FunSpec.setterBuilder()
+          .addModifiers(EXTERNAL)
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |var foo: kotlin.String
+      |  external get
+      |  external set
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalGetterCannotHaveBody() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("foo", String::class)
+        .getter(
+          FunSpec.getterBuilder()
+            .addModifiers(EXTERNAL)
+            .addStatement("return %S", "foo")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("external getter cannot have code")
+  }
+
+  @Test fun publicGetterAndSetter() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .mutable()
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(PUBLIC)
+          .addStatement("return %S", "_foo")
+          .build(),
+      )
+      .setter(
+        FunSpec.setterBuilder()
+          .addModifiers(PUBLIC)
+          .addParameter("value", String::class)
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |var foo: kotlin.String
+      |  public get() = "_foo"
+      |  public set(`value`) {
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineSingleAccessorVal() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return %S", "foo")
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |inline val foo: kotlin.String
+      |  get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineSingleAccessorVar() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .mutable()
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return %S", "foo")
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |var foo: kotlin.String
+      |  inline get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineBothAccessors() {
+    val prop = PropertySpec.builder("foo", String::class.asTypeName())
+      .mutable()
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return %S", "foo")
+          .build(),
+      )
+      .setter(
+        FunSpec.setterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addParameter("value", String::class)
+          .build(),
+      )
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |inline var foo: kotlin.String
+      |  get() = "foo"
+      |  set(`value`) {
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineForbiddenOnProperty() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("foo", String::class)
+        .addModifiers(KModifier.INLINE)
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "KotlinPoet doesn't allow setting the inline modifier on " +
+        "properties. You should mark either the getter, the setter, or both inline.",
+    )
+  }
+
+  @Test fun equalsAndHashCode() {
+    val type = Int::class
+    var a = PropertySpec.builder("foo", type).build()
+    var b = PropertySpec.builder("foo", type).build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = PropertySpec.builder("FOO", type, KModifier.PUBLIC, KModifier.LATEINIT).build()
+    b = PropertySpec.builder("FOO", type, KModifier.PUBLIC, KModifier.LATEINIT).build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun escapeKeywordInPropertyName() {
+    val prop = PropertySpec.builder("object", String::class)
+      .build()
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |val `object`: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeKeywordInVariableName() {
+    val prop = PropertySpec.builder("object", String::class)
+      .mutable()
+      .build()
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |var `object`: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalTopLevel() {
+    val prop = PropertySpec.builder("foo", String::class)
+      .addModifiers(KModifier.EXTERNAL)
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |external val foo: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapePunctuationInPropertyName() {
+    val prop = PropertySpec.builder("with-hyphen", String::class)
+      .build()
+
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |val `with-hyphen`: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun generalBuilderEqualityTest() {
+    val originatingElement = FakeElement()
+    val prop = PropertySpec.builder("tacos", Int::class)
+      .mutable()
+      .addAnnotation(ClassName("com.squareup.kotlinpoet", "Vegan"))
+      .addKdoc("Can make it vegan!")
+      .addModifiers(KModifier.PUBLIC)
+      .addTypeVariable(TypeVariableName("T"))
+      .delegate("Delegates.notNull()")
+      .receiver(Int::class)
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return %S", 42)
+          .build(),
+      )
+      .setter(
+        FunSpec.setterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addParameter("value", Int::class)
+          .build(),
+      )
+      .addOriginatingElement(originatingElement)
+      .build()
+
+    val newProp = prop.toBuilder().build()
+    assertThat(newProp).isEqualTo(prop)
+    assertThat(newProp.originatingElements).containsExactly(originatingElement)
+  }
+
+  @Test fun modifyModifiers() {
+    val builder = PropertySpec
+      .builder("word", String::class)
+      .addModifiers(PRIVATE)
+
+    builder.modifiers.clear()
+    builder.modifiers.add(KModifier.INTERNAL)
+
+    assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = PropertySpec
+      .builder("word", String::class)
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class.asClassName())
+          .addMember("name = %S", "jvmWord")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+      .addMember("name = %S", "javaWord")
+      .build()
+    builder.annotations.clear()
+    builder.annotations.add(javaWord)
+
+    assertThat(builder.build().annotations).containsExactly(javaWord)
+  }
+
+  // https://github.com/square/kotlinpoet/issues/437
+  @Test fun typeVariable() {
+    val t = TypeVariableName("T", Any::class)
+    val prop = PropertySpec.builder("someFunction", t, PRIVATE)
+      .addTypeVariable(t)
+      .receiver(KClass::class.asClassName().parameterizedBy(t))
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return stuff as %T", t)
+          .build(),
+      )
+      .build()
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |private inline val <T : kotlin.Any> kotlin.reflect.KClass<T>.someFunction: T
+      |  get() = stuff as T
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun typeVariablesWithWhere() {
+    val t = TypeVariableName("T", Serializable::class, Cloneable::class)
+    val r = TypeVariableName("R", Any::class)
+    val function = Function::class.asClassName().parameterizedBy(t, r)
+    val prop = PropertySpec.builder("property", String::class, PRIVATE)
+      .receiver(function)
+      .addTypeVariables(listOf(t, r))
+      .getter(
+        FunSpec.getterBuilder()
+          .addStatement("return %S", "")
+          .build(),
+      )
+      .build()
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |private val <T, R : kotlin.Any> java.util.function.Function<T, R>.`property`: kotlin.String where T : java.io.Serializable, T : kotlin.Cloneable
+      |  get() = ""
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun reifiedTypeVariable() {
+    val t = TypeVariableName("T").copy(reified = true)
+    val prop = PropertySpec.builder("someFunction", t, PRIVATE)
+      .addTypeVariable(t)
+      .receiver(KClass::class.asClassName().parameterizedBy(t))
+      .getter(
+        FunSpec.getterBuilder()
+          .addModifiers(KModifier.INLINE)
+          .addStatement("return stuff as %T", t)
+          .build(),
+      )
+      .build()
+    assertThat(prop.toString()).isEqualTo(
+      """
+      |private inline val <reified T> kotlin.reflect.KClass<T>.someFunction: T
+      |  get() = stuff as T
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun reifiedTypeVariableNotAllowedWhenNoAccessors() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class)
+        .addTypeVariable(TypeVariableName("T").copy(reified = true))
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "only type parameters of properties with inline getters and/or setters can be reified!",
+    )
+  }
+
+  @Test fun reifiedTypeVariableNotAllowedWhenGetterNotInline() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class)
+        .addTypeVariable(TypeVariableName("T").copy(reified = true))
+        .getter(
+          FunSpec.getterBuilder()
+            .addStatement("return %S", "")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "only type parameters of properties with inline getters and/or setters can be reified!",
+    )
+  }
+
+  @Test fun reifiedTypeVariableNotAllowedWhenSetterNotInline() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class.asTypeName())
+        .mutable()
+        .addTypeVariable(TypeVariableName("T").copy(reified = true))
+        .setter(
+          FunSpec.setterBuilder()
+            .addParameter("value", String::class)
+            .addStatement("println()")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "only type parameters of properties with inline getters and/or setters can be reified!",
+    )
+  }
+
+  @Test fun reifiedTypeVariableNotAllowedWhenOnlySetterIsInline() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class.asTypeName())
+        .mutable()
+        .addTypeVariable(TypeVariableName("T").copy(reified = true))
+        .getter(
+          FunSpec.getterBuilder()
+            .addStatement("return %S", "")
+            .build(),
+        )
+        .setter(
+          FunSpec.setterBuilder()
+            .addModifiers(KModifier.INLINE)
+            .addParameter("value", String::class)
+            .addStatement("println()")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "only type parameters of properties with inline getters and/or setters can be reified!",
+    )
+  }
+
+  @Test fun setterNotAllowedWhenPropertyIsNotMutable() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class.asTypeName())
+        .setter(
+          FunSpec.setterBuilder()
+            .addModifiers(KModifier.INLINE)
+            .addParameter("value", String::class)
+            .addStatement("println()")
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("only a mutable property can have a setter")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/462
+  @Test fun codeBlockInitializer() {
+    val param = ParameterSpec.builder("arg", ANY).build()
+    val initializer = CodeBlock.builder()
+      .beginControlFlow("{ %L ->", param)
+      .addStatement("println(\"arg=\$%N\")", param)
+      .endControlFlow()
+      .build()
+    val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+    val property = PropertySpec.builder("property", lambdaTypeName)
+      .initializer(initializer)
+      .build()
+    assertThat(property.toString()).isEqualTo(
+      """
+      |val `property`: com.example.SomeTypeAlias = { arg: kotlin.Any ->
+      |  println("arg=${'$'}arg")
+      |}
+      |
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun doublePropertyInitialization() {
+    val codeBlockInitializer = PropertySpec.builder("listA", String::class)
+      .initializer(CodeBlock.builder().add("foo").build())
+      .initializer(CodeBlock.builder().add("bar").build())
+      .build()
+
+    assertThat(CodeBlock.of("bar")).isEqualTo(codeBlockInitializer.initializer)
+
+    val formatInitializer = PropertySpec.builder("listA", String::class)
+      .initializer("foo")
+      .initializer("bar")
+      .build()
+
+    assertThat(CodeBlock.of("bar")).isEqualTo(formatInitializer.initializer)
+  }
+
+  @Test fun propertyKdocWithoutLinebreak() {
+    val property = PropertySpec.builder("topping", String::class)
+      .addKdoc("The topping you want on your pizza")
+      .build()
+    assertThat(property.toString()).isEqualTo(
+      """
+      |/**
+      | * The topping you want on your pizza
+      | */
+      |val topping: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun propertyKdocWithLinebreak() {
+    val property = PropertySpec.builder("topping", String::class)
+      .addKdoc("The topping you want on your pizza\n")
+      .build()
+    assertThat(property.toString()).isEqualTo(
+      """
+      |/**
+      | * The topping you want on your pizza
+      | */
+      |val topping: kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun getterKdoc() {
+    val property = PropertySpec.builder("amount", Int::class)
+      .initializer("4")
+      .getter(
+        FunSpec.getterBuilder()
+          .addKdoc("Simple multiplier")
+          .addStatement("return %L * 5", "field")
+          .build(),
+      )
+      .build()
+
+    assertThat(property.toString()).isEqualTo(
+      """
+      |val amount: kotlin.Int = 4
+      |  /**
+      |   * Simple multiplier
+      |   */
+      |  get() = field * 5
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun constProperty() {
+    val text = "This is a long string with a newline\nin the middle."
+    val spec = FileSpec.builder("testsrc", "Test")
+      .addProperty(
+        PropertySpec.builder("FOO", String::class, KModifier.CONST)
+          .initializer("%S", text)
+          .build(),
+      )
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package testsrc
+      |
+      |import kotlin.String
+      |
+      |public const val FOO: String = "This is a long string with a newline\nin the middle."
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedLambdaType() {
+    val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+    val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+    val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(PropertySpec.builder("foo", type).build())
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public val foo: @Annotation () -> Unit
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1002
+  @Test fun visibilityOmittedOnAccessors() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class, PRIVATE)
+          .mutable()
+          .getter(
+            FunSpec.getterBuilder()
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .setter(
+            FunSpec.setterBuilder()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      //language=kotlin
+      """
+        package com.squareup.tacos
+
+        import kotlin.String
+
+        private var foo: String
+          get() = "foo"
+          set(foo) {
+          }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun varWithContextReceiverWithoutCustomAccessors() {
+    val mutablePropertySpecBuilder = {
+      PropertySpec.builder("foo", STRING)
+        .mutable()
+        .contextReceivers(INT)
+    }
+
+    assertThrows<IllegalArgumentException> {
+      mutablePropertySpecBuilder()
+        .getter(
+          FunSpec.getterBuilder()
+            .build(),
+        )
+        .build()
+    }.hasMessageThat()
+      .isEqualTo("mutable properties with context receivers require a $SETTER")
+
+    assertThrows<IllegalArgumentException> {
+      mutablePropertySpecBuilder()
+        .setter(
+          FunSpec.setterBuilder()
+            .build(),
+        )
+        .build()
+    }.hasMessageThat()
+      .isEqualTo("properties with context receivers require a $GETTER")
+  }
+
+  @Test fun valWithContextReceiverWithoutGetter() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("foo", STRING)
+        .mutable(false)
+        .contextReceivers(INT)
+        .build()
+    }.hasMessageThat()
+      .isEqualTo("properties with context receivers require a $GETTER")
+  }
+
+  @Test fun varWithContextReceiver() {
+    val propertySpec = PropertySpec.builder("foo", INT)
+      .mutable()
+      .contextReceivers(STRING)
+      .getter(
+        FunSpec.getterBuilder()
+          .addStatement("return \"\"")
+          .build(),
+      )
+      .setter(
+        FunSpec.setterBuilder()
+          .addParameter(
+            ParameterSpec.builder("value", STRING)
+              .build(),
+          )
+          .addStatement("")
+          .build(),
+      )
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |var foo: kotlin.Int
+      |  get() = ""
+      |  set(`value`) {
+      |
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun valWithContextReceiver() {
+    val propertySpec = PropertySpec.builder("foo", INT)
+      .mutable(false)
+      .contextReceivers(STRING)
+      .getter(
+        FunSpec.getterBuilder()
+          .addStatement("return length")
+          .build(),
+      )
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |val foo: kotlin.Int
+      |  get() = length
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @OptIn(DelicateKotlinPoetApi::class)
+  @Test
+  fun annotatedValWithContextReceiver() {
+    val propertySpec = PropertySpec.builder("foo", INT)
+      .mutable(false)
+      .addAnnotation(AnnotationSpec.get(TestAnnotation()))
+      .contextReceivers(STRING)
+      .getter(
+        FunSpec.getterBuilder()
+          .addStatement("return length")
+          .build(),
+      )
+      .build()
+
+    assertThat(propertySpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |@com.squareup.kotlinpoet.PropertySpecTest.TestAnnotation
+      |val foo: kotlin.Int
+      |  get() = length
+      |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt
new file mode 100644
index 0000000..49bec35
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class StringsTest {
+  @Test fun singleLineStringWithDollarSymbols() {
+    val stringWithTemplate = "$" + "annoyingUser" + " is annoying."
+    val funSpec = FunSpec.builder("getString")
+      .addStatement("return %S", stringWithTemplate)
+      .build()
+    assertThat(funSpec.toString())
+      .isEqualTo("public fun getString() = \"\${\'\$\'}annoyingUser is annoying.\"\n")
+  }
+
+  @Test fun multilineStringWithDollarSymbols() {
+    val stringWithTemplate = "Some string\n" + "$" + "annoyingUser" + " is annoying."
+    val funSpec = FunSpec.builder("getString")
+      .addStatement("return %S", stringWithTemplate)
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      "public fun getString() = \"\"\"\n" +
+        "|Some string\n" +
+        "|\${\'\$\'}annoyingUser is annoying.\n" +
+        "\"\"\".trimMargin()\n",
+    )
+  }
+
+  @Test fun singleLineStringTemplate() {
+    val stringWithTemplate = "$" + "annoyingUser" + " is annoying."
+    val funSpec = FunSpec.builder("getString")
+      .addStatement("return %P", stringWithTemplate)
+      .build()
+    assertThat(funSpec.toString())
+      .isEqualTo("public fun getString() = \"\"\"\$annoyingUser is annoying.\"\"\"\n")
+  }
+
+  @Test fun multilineStringTemplate() {
+    val stringWithTemplate = "Some string\n" + "$" + "annoyingUser" + " is annoying."
+    val funSpec = FunSpec.builder("getString")
+      .addStatement("return %P", stringWithTemplate)
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      "public fun getString() = \"\"\"\n" +
+        "|Some string\n" +
+        "|\$annoyingUser is annoying.\n" +
+        "\"\"\".trimMargin()\n",
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/572
+  @Test fun templateStringWithStringLiteralReference() {
+    val string = "SELECT * FROM socialFeedItem WHERE message IS NOT NULL AND userId \${ if (userId == null) \"IS\" else \"=\" } ?1 ORDER BY datetime(creation_time) DESC"
+    val funSpec = FunSpec.builder("getString")
+      .addStatement("return %P", string)
+      .build()
+    assertThat(funSpec.toString())
+      .isEqualTo("public fun getString() = \"\"\"SELECT * FROM socialFeedItem WHERE message IS NOT NULL AND userId \${ if (userId == null) \"IS\" else \"=\" } ?1 ORDER BY datetime(creation_time) DESC\"\"\"\n")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt
new file mode 100644
index 0000000..504798b
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class TaggableTest(val builder: Taggable.Builder<*>) {
+
+  companion object {
+    @JvmStatic
+    @Parameterized.Parameters(name = "{0}")
+    fun data() = arrayOf(
+      AnnotationSpec.builder(JvmStatic::class),
+      FileSpec.builder("test", "Test"),
+      FunSpec.builder("test"),
+      ParameterSpec.builder("test", String::class.asClassName()),
+      PropertySpec.builder("test", String::class.asClassName()),
+      TypeAliasSpec.builder("Test", String::class.asClassName()),
+      TypeSpec.classBuilder("Test"),
+    )
+  }
+
+  @Before fun setUp() {
+    builder.tags.clear()
+  }
+
+  @Test fun builderShouldMakeDefensiveCopy() {
+    builder.tag(String::class, "test")
+    val taggable = builder.buildTaggable()
+    builder.tags.remove(String::class)
+    assertThat(taggable.tag<String>()).isEqualTo("test")
+  }
+
+  @Test fun missingShouldBeNull() {
+    val taggable = builder.buildTaggable()
+    assertThat(taggable.tag<Int>()).isNull()
+  }
+
+  @Test fun kclassParamFlow() {
+    builder.tag(String::class, "test")
+    val taggable = builder.buildTaggable()
+    assertThat(taggable.tag(String::class)).isEqualTo("test")
+  }
+
+  @Test fun javaClassParamFlow() {
+    builder.tag(String::class.java, "test")
+    val taggable = builder.buildTaggable()
+    assertThat(taggable.tag(String::class.java)).isEqualTo("test")
+  }
+
+  @Test fun kclassInJavaClassOut() {
+    builder.tag(String::class, "test")
+    val taggable = builder.buildTaggable()
+    assertThat(taggable.tag(String::class.java)).isEqualTo("test")
+  }
+
+  @Test fun javaClassInkClassOut() {
+    builder.tag(String::class.java, "test")
+    val taggable = builder.buildTaggable()
+    assertThat(taggable.tag(String::class)).isEqualTo("test")
+  }
+
+  private fun Taggable.Builder<*>.buildTaggable(): Taggable {
+    // Apply blocks test inline builder tag functions don't break the chain. Result is discarded
+    return when (this) {
+      is AnnotationSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .addMember(CodeBlock.of(""))
+          .build()
+      }
+      is FileSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .addFileComment("Test")
+          .build()
+      }
+      is FunSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .returns(String::class)
+          .build()
+      }
+      is ParameterSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .addModifiers(CROSSINLINE)
+          .build()
+      }
+      is PropertySpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .initializer(CodeBlock.of(""))
+          .build()
+      }
+      is TypeAliasSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .addKdoc(CodeBlock.of(""))
+          .build()
+      }
+      is TypeSpec.Builder -> build().apply {
+        toBuilder()
+          .tag(1)
+          .addKdoc(CodeBlock.of(""))
+          .build()
+      }
+      else -> TODO("Unsupported type ${this::class.simpleName}")
+    }
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt
new file mode 100644
index 0000000..bb24d56
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package com.squareup.kotlinpoet
+
+import java.io.OutputStream
+import java.nio.file.FileSystem
+import java.nio.file.Files
+import java.nio.file.Path
+import javax.annotation.processing.Filer
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ElementVisitor
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Name
+import javax.lang.model.type.TypeMirror
+import javax.tools.FileObject
+import javax.tools.JavaFileManager
+import javax.tools.JavaFileObject
+import javax.tools.SimpleJavaFileObject
+
+internal class TestFiler(
+  fileSystem: FileSystem,
+  private val fileSystemRoot: Path,
+) : Filer {
+
+  internal inner class Source(private val path: Path) :
+    SimpleJavaFileObject(path.toUri(), JavaFileObject.Kind.SOURCE) {
+    override fun openOutputStream(): OutputStream {
+      val parent = path.parent
+      if (!Files.exists(parent)) fileSystemProvider.createDirectory(parent)
+      return fileSystemProvider.newOutputStream(path)
+    }
+  }
+
+  private val separator = fileSystem.separator
+  private val fileSystemProvider = fileSystem.provider()
+  private val originatingElementsMap = mutableMapOf<Path, List<Element>>()
+
+  fun getOriginatingElements(path: Path) = originatingElementsMap[path] ?: throw NullPointerException("Could not find $path")
+
+  override fun createSourceFile(
+    name: CharSequence,
+    vararg originatingElements: Element,
+  ): JavaFileObject {
+    val relative = name.toString().replace(".", separator) + ".kt" // Assumes well-formed.
+    val path = fileSystemRoot.resolve(relative)
+    originatingElementsMap[path] = originatingElements.toList()
+    return Source(path)
+  }
+
+  override fun createClassFile(name: CharSequence, vararg originatingElements: Element) =
+    throw UnsupportedOperationException("Not implemented.")
+
+  override fun createResource(
+    location: JavaFileManager.Location,
+    pkg: CharSequence,
+    relativeName: CharSequence,
+    vararg originatingElements: Element,
+  ): FileObject {
+    val relative = pkg.toString().replace(".", separator) + separator + relativeName
+    val path = fileSystemRoot.resolve(relative)
+    originatingElementsMap[path] = originatingElements.toList()
+    return Source(path)
+  }
+
+  override fun getResource(
+    location: JavaFileManager.Location,
+    pkg: CharSequence,
+    relativeName: CharSequence,
+  ) = throw UnsupportedOperationException("Not implemented.")
+}
+
+internal class FakeElement : Element {
+
+  override fun getModifiers(): MutableSet<Modifier> {
+    TODO()
+  }
+
+  override fun getSimpleName(): Name {
+    TODO()
+  }
+
+  override fun getKind(): ElementKind {
+    TODO()
+  }
+
+  override fun asType(): TypeMirror {
+    TODO()
+  }
+
+  override fun getEnclosingElement(): Element {
+    TODO()
+  }
+
+  override fun <R : Any?, P : Any?> accept(v: ElementVisitor<R, P>?, p: P): R {
+    TODO()
+  }
+
+  override fun <A : Annotation?> getAnnotationsByType(annotationType: Class<A>?): Array<A> {
+    TODO()
+  }
+
+  override fun <A : Annotation?> getAnnotation(annotationType: Class<A>?): A {
+    TODO()
+  }
+
+  override fun getAnnotationMirrors(): MutableList<out AnnotationMirror> {
+    TODO()
+  }
+
+  override fun getEnclosedElements(): MutableList<out Element> {
+    TODO()
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt
new file mode 100644
index 0000000..f5c7eb2
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+import kotlin.test.Test
+
+class TypeAliasSpecTest {
+
+  @Test fun simpleTypeAlias() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |public typealias Word = kotlin.String
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun typeVariable() {
+    val v = TypeVariableName("V")
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", List::class.asClassName().parameterizedBy(v))
+      .addTypeVariable(v)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |public typealias Word<V> = kotlin.collections.List<V>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun publicVisibility() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addModifiers(KModifier.PUBLIC)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |public typealias Word = kotlin.String
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun internalVisibility() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addModifiers(KModifier.INTERNAL)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |internal typealias Word = kotlin.String
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun privateVisibility() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addModifiers(KModifier.PRIVATE)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |private typealias Word = kotlin.String
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun implTypeAlias() {
+    val typeName = AtomicReference::class.asClassName().parameterizedBy(TypeVariableName("V"))
+    val typeAliasSpec = TypeAliasSpec
+      .builder("AtomicRef", typeName)
+      .addModifiers(KModifier.ACTUAL)
+      .addTypeVariable(TypeVariableName("V"))
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+        |public actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun kdoc() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addKdoc("Word is just a type alias for [String](%T).\n", String::class)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * Word is just a type alias for [String](kotlin.String).
+      | */
+      |public typealias Word = kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotations() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addAnnotation(
+        AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+          .addMember("value = %S", "words!")
+          .build(),
+      )
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+      |@com.squareup.kotlinpoet.TypeAliasAnnotation(value = "words!")
+      |public typealias Word = kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun kdocWithoutNewLine() {
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Word", String::class)
+      .addKdoc("Word is just a type alias for [String](%T).", String::class)
+      .build()
+
+    assertThat(typeAliasSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * Word is just a type alias for [String](kotlin.String).
+      | */
+      |public typealias Word = kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun equalsAndHashCode() {
+    val a = TypeAliasSpec.builder("Word", String::class).addModifiers(KModifier.PUBLIC).build()
+    val b = TypeAliasSpec.builder("Word", String::class).addModifiers(KModifier.PUBLIC).build()
+
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun generalBuilderEqualityTest() {
+    val typeParam = TypeVariableName("V")
+    val typeAliasSpec = TypeAliasSpec
+      .builder("Bio", Pair::class.parameterizedBy(String::class, String::class))
+      .addKdoc("First nand Last Name.\n")
+      .addModifiers(KModifier.PUBLIC)
+      .addTypeVariable(typeParam)
+      .build()
+    assertThat(typeAliasSpec.toBuilder().build()).isEqualTo(typeAliasSpec)
+  }
+
+  @Test fun modifyModifiers() {
+    val builder = TypeAliasSpec
+      .builder("Word", String::class)
+      .addModifiers(KModifier.PRIVATE)
+
+    builder.modifiers.clear()
+    builder.modifiers.add(KModifier.INTERNAL)
+
+    assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+  }
+
+  @Test fun modifyTypeVariableNames() {
+    val builder = TypeAliasSpec
+      .builder("Word", String::class)
+      .addTypeVariable(TypeVariableName("V"))
+
+    val tVar = TypeVariableName("T")
+    builder.typeVariables.clear()
+    builder.typeVariables.add(tVar)
+
+    assertThat(builder.build().typeVariables).containsExactly(tVar)
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = TypeAliasSpec
+      .builder("Word", String::class)
+      .addAnnotation(
+        AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+          .addMember("value = %S", "value1")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+      .addMember("value = %S", "value2")
+      .build()
+    builder.annotations.clear()
+    builder.annotations.add(javaWord)
+
+    assertThat(builder.build().annotations).containsExactly(javaWord)
+  }
+
+  @Test fun nameEscaping() {
+    val typeAlias = TypeAliasSpec.builder("fun", String::class).build()
+    assertThat(typeAlias.toString()).isEqualTo(
+      """
+      |public typealias `fun` = kotlin.String
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedLambdaType() {
+    val annotation = AnnotationSpec.builder(ClassName("", "Annotation")).build()
+    val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+    val spec = TypeAliasSpec.builder("lambda", type).build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |public typealias lambda = @Annotation () -> kotlin.Unit
+      |
+      """.trimMargin(),
+    )
+  }
+}
+
+@Retention(RUNTIME)
+@Target(TYPEALIAS)
+annotation class TypeAliasAnnotation(val value: String)
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt
new file mode 100644
index 0000000..3c16242
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TypeNameKotlinTest {
+
+  @Test
+  fun typeNameOf_simple() {
+    val type = typeNameOf<TypeNameKotlinTest>()
+    assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest")
+  }
+
+  @Test
+  fun typeNameOf_simple_intrinsic() {
+    val type = typeNameOf<String>()
+    assertThat(type.toString()).isEqualTo("kotlin.String")
+  }
+
+  @Test
+  fun typeNameOf_array_primitive() {
+    val type = typeNameOf<IntArray>()
+    assertThat(type.toString()).isEqualTo("kotlin.IntArray")
+  }
+
+  @Test
+  fun typeNameOf_array_parameterized() {
+    val type = typeNameOf<Array<String>>()
+    assertThat(type.toString()).isEqualTo("kotlin.Array<kotlin.String>")
+  }
+
+  @Test
+  fun typeNameOf_nullable() {
+    val type = typeNameOf<String?>()
+    assertThat(type.toString()).isEqualTo("kotlin.String?")
+  }
+
+  @Test
+  fun typeNameOf_generic() {
+    val type = typeNameOf<List<String>>()
+    assertThat(type.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+  }
+
+  @Test
+  fun typeNameOf_generic_wildcard_out() {
+    val type = typeNameOf<GenericType<out String>>()
+    assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<out kotlin.String>")
+  }
+
+  @Test
+  fun typeNameOf_generic_wildcard_in() {
+    val type = typeNameOf<GenericType<in String>>()
+    assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<in kotlin.String>")
+  }
+
+  @Test
+  fun typeNameOf_complex() {
+    val type = typeNameOf<Map<String, List<Map<*, GenericType<in Set<Array<GenericType<out String>?>>>>>>>()
+    assertThat(type.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.collections.List<kotlin.collections.Map<*, com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<in kotlin.collections.Set<kotlin.Array<com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<out kotlin.String>?>>>>>>")
+  }
+
+  @Suppress("unused")
+  class GenericType<T>
+
+  @Test
+  fun tag() {
+    val type = typeNameOf<String>().copy(tags = mapOf(String::class to "Test"))
+    assertThat(type.tag<String>()).isEqualTo("Test")
+  }
+
+  @Test
+  fun existingTagsShouldBePreserved() {
+    val type = typeNameOf<String>().copy(tags = mapOf(String::class to "Test"))
+    val copied = type.copy(nullable = true)
+    assertThat(copied.tag<String>()).isEqualTo("Test")
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java
new file mode 100644
index 0000000..70019f9
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+public class TypeNameTest {
+
+  protected <E extends Enum<E>> E generic(E[] values) {
+    return values[0];
+  }
+
+  protected static class TestGeneric<T> {
+    class Inner {}
+
+    class InnerGeneric<T2> {}
+
+    static class NestedNonGeneric {}
+  }
+
+  protected static TestGeneric<String>.Inner testGenericStringInner() {
+    return null;
+  }
+
+  protected static TestGeneric<Integer>.Inner testGenericIntInner() {
+    return null;
+  }
+
+  protected static TestGeneric<Short>.InnerGeneric<Long> testGenericInnerLong() {
+    return null;
+  }
+
+  protected static TestGeneric<Short>.InnerGeneric<Integer> testGenericInnerInt() {
+    return null;
+  }
+
+  protected static TestGeneric.NestedNonGeneric testNestedNonGeneric() {
+    return null;
+  }
+
+  @Test public void genericType() throws Exception {
+    Method recursiveEnum = getClass().getDeclaredMethod("generic", Enum[].class);
+    TypeNames.get(recursiveEnum.getReturnType());
+    TypeNames.get(recursiveEnum.getGenericReturnType());
+    TypeName genericTypeName = TypeNames.get(recursiveEnum.getParameterTypes()[0]);
+    TypeNames.get(recursiveEnum.getGenericParameterTypes()[0]);
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).contains("Enum");
+  }
+
+  @Test public void innerClassInGenericType() throws Exception {
+    Method genericStringInner = getClass().getDeclaredMethod("testGenericStringInner");
+    TypeNames.get(genericStringInner.getReturnType());
+    TypeName genericTypeName = TypeNames.get(genericStringInner.getGenericReturnType());
+    assertNotEquals(TypeNames.get(genericStringInner.getGenericReturnType()),
+        TypeNames.get(getClass().getDeclaredMethod("testGenericIntInner").getGenericReturnType()));
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + "<java.lang.String>.Inner");
+  }
+
+  @Test public void innerGenericInGenericType() throws Exception {
+    Method genericStringInner = getClass().getDeclaredMethod("testGenericInnerLong");
+    TypeNames.get(genericStringInner.getReturnType());
+    TypeName genericTypeName = TypeNames.get(genericStringInner.getGenericReturnType());
+    assertNotEquals(TypeNames.get(genericStringInner.getGenericReturnType()),
+        TypeNames.get(getClass().getDeclaredMethod("testGenericInnerInt").getGenericReturnType()));
+
+    // Make sure the generic argument is present
+    assertThat(genericTypeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + "<java.lang.Short>.InnerGeneric<java.lang.Long>");
+  }
+
+  @Test public void innerStaticInGenericType() throws Exception {
+    Method staticInGeneric = getClass().getDeclaredMethod("testNestedNonGeneric");
+    TypeNames.get(staticInGeneric.getReturnType());
+    TypeName typeName = TypeNames.get(staticInGeneric.getGenericReturnType());
+
+    // Make sure there are no generic arguments
+    assertThat(typeName.toString()).isEqualTo(
+        TestGeneric.class.getCanonicalName() + ".NestedNonGeneric");
+  }
+
+  @Test public void equalsAndHashCodePrimitive() {
+    assertEqualsHashCodeAndToString(TypeNames.BOOLEAN, TypeNames.BOOLEAN);
+    assertEqualsHashCodeAndToString(TypeNames.BYTE, TypeNames.BYTE);
+    assertEqualsHashCodeAndToString(TypeNames.CHAR, TypeNames.CHAR);
+    assertEqualsHashCodeAndToString(TypeNames.DOUBLE, TypeNames.DOUBLE);
+    assertEqualsHashCodeAndToString(TypeNames.FLOAT, TypeNames.FLOAT);
+    assertEqualsHashCodeAndToString(TypeNames.INT, TypeNames.INT);
+    assertEqualsHashCodeAndToString(TypeNames.LONG, TypeNames.LONG);
+    assertEqualsHashCodeAndToString(TypeNames.SHORT, TypeNames.SHORT);
+    assertEqualsHashCodeAndToString(TypeNames.UNIT, TypeNames.UNIT);
+  }
+
+  @Test public void equalsAndHashCodeClassName() {
+    assertEqualsHashCodeAndToString(ClassNames.get(Object.class), ClassNames.get(Object.class));
+    assertEqualsHashCodeAndToString(TypeNames.get(Object.class), ClassNames.get(Object.class));
+    assertEqualsHashCodeAndToString(ClassName.bestGuess("java.lang.Object"),
+        ClassNames.get(Object.class));
+  }
+
+  @Test public void equalsAndHashCodeParameterizedTypeName() {
+    assertEqualsHashCodeAndToString(ParameterizedTypeName.get(List.class, Object.class),
+        ParameterizedTypeName.get(List.class, Object.class));
+    assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Set.class, UUID.class),
+        ParameterizedTypeName.get(Set.class, UUID.class));
+    assertNotEquals(ClassNames.get(List.class), ParameterizedTypeName.get(List.class,
+        String.class));
+  }
+
+  @Test public void equalsAndHashCodeTypeVariableName() {
+    assertEqualsHashCodeAndToString(TypeVariableName.get("A"),
+        TypeVariableName.get("A"));
+    TypeVariableName typeVar1 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+    TypeVariableName typeVar2 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+    assertEqualsHashCodeAndToString(typeVar1, typeVar2);
+  }
+
+  @Test public void equalsAndHashCodeWildcardTypeName() {
+    assertEqualsHashCodeAndToString(WildcardTypeName.producerOf(Object.class),
+        WildcardTypeName.producerOf(Object.class));
+    assertEqualsHashCodeAndToString(WildcardTypeName.producerOf(Serializable.class),
+        WildcardTypeName.producerOf(Serializable.class));
+    assertEqualsHashCodeAndToString(WildcardTypeName.consumerOf(String.class),
+        WildcardTypeName.consumerOf(String.class));
+  }
+
+  private void assertEqualsHashCodeAndToString(TypeName a, TypeName b) {
+    assertEquals(a.toString(), b.toString());
+    assertThat(a.equals(b)).isTrue();
+    assertThat(a.hashCode()).isEqualTo(b.hashCode());
+    assertFalse(a.equals(null));
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
new file mode 100644
index 0000000..cf6c3e2
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
@@ -0,0 +1,5364 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.collect.ImmutableMap
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.KModifier.IN
+import com.squareup.kotlinpoet.KModifier.INNER
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.jvm.throws
+import java.io.IOException
+import java.io.Serializable
+import java.lang.Deprecated
+import java.math.BigDecimal
+import java.util.AbstractSet
+import java.util.Collections
+import java.util.Comparator
+import java.util.EventListener
+import java.util.Locale
+import java.util.Random
+import java.util.concurrent.Callable
+import java.util.function.Consumer
+import java.util.logging.Logger
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+import org.junit.Rule
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class TypeSpecTest {
+  private val tacosPackage = "com.squareup.tacos"
+
+  @Rule @JvmField
+  val compilation = CompilationRule()
+
+  private fun getElement(`class`: Class<*>): TypeElement {
+    return compilation.elements.getTypeElement(`class`.canonicalName)
+  }
+
+  private fun getElement(`class`: KClass<*>): TypeElement {
+    return getElement(`class`.java)
+  }
+
+  @Test fun basic() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("toString")
+          .addModifiers(KModifier.PUBLIC, KModifier.FINAL, KModifier.OVERRIDE)
+          .returns(String::class)
+          .addStatement("return %S", "taco")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public final override fun toString(): String = "taco"
+        |}
+        |
+      """.trimMargin(),
+    )
+    assertEquals(1906837485, taco.hashCode().toLong()) // Update expected number if source changes.
+  }
+
+  @Test fun interestingTypes() {
+    val listOfAny = List::class.asClassName().parameterizedBy(STAR)
+    val listOfExtends = List::class.asClassName()
+      .parameterizedBy(WildcardTypeName.producerOf(Serializable::class))
+    val listOfSuper = List::class.asClassName()
+      .parameterizedBy(WildcardTypeName.consumerOf(String::class))
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty("star", listOfAny)
+      .addProperty("outSerializable", listOfExtends)
+      .addProperty("inString", listOfSuper)
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import kotlin.String
+        |import kotlin.collections.List
+        |
+        |public class Taco {
+        |  public val star: List<*>
+        |
+        |  public val outSerializable: List<out Serializable>
+        |
+        |  public val inString: List<in String>
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun anonymousInnerClass() {
+    val foo = ClassName(tacosPackage, "Foo")
+    val bar = ClassName(tacosPackage, "Bar")
+    val thingThang = ClassName(tacosPackage, "Thing", "Thang")
+    val thingThangOfFooBar = thingThang.parameterizedBy(foo, bar)
+    val thung = ClassName(tacosPackage, "Thung")
+    val simpleThung = ClassName(tacosPackage, "SimpleThung")
+    val thungOfSuperBar = thung.parameterizedBy(WildcardTypeName.consumerOf(bar))
+    val thungOfSuperFoo = thung.parameterizedBy(WildcardTypeName.consumerOf(foo))
+    val simpleThungOfBar = simpleThung.parameterizedBy(bar)
+
+    val thungParameter = ParameterSpec.builder("thung", thungOfSuperFoo)
+      .build()
+    val aSimpleThung = TypeSpec.anonymousClassBuilder()
+      .superclass(simpleThungOfBar)
+      .addSuperclassConstructorParameter("%N", thungParameter)
+      .addFunction(
+        FunSpec.builder("doSomething")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .addParameter("bar", bar)
+          .addCode("/* code snippets */\n")
+          .build(),
+      )
+      .build()
+    val aThingThang = TypeSpec.anonymousClassBuilder()
+      .superclass(thingThangOfFooBar)
+      .addFunction(
+        FunSpec.builder("call")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(thungOfSuperBar)
+          .addParameter(thungParameter)
+          .addStatement("return %L", aSimpleThung)
+          .build(),
+      )
+      .build()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("NAME", thingThangOfFooBar)
+          .initializer("%L", aThingThang)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public val NAME: Thing.Thang<Foo, Bar> = object : Thing.Thang<Foo, Bar>() {
+        |    public override fun call(thung: Thung<in Foo>): Thung<in Bar> = object : SimpleThung<Bar>(thung)
+        |        {
+        |      public override fun doSomething(bar: Bar): Unit {
+        |        /* code snippets */
+        |      }
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun anonymousClassWithSuperClassConstructorCall() {
+    val superclass = ArrayList::class.parameterizedBy(String::class)
+    val anonymousClass = TypeSpec.anonymousClassBuilder()
+      .addSuperclassConstructorParameter("%L", "4")
+      .superclass(superclass)
+      .build()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("names", superclass)
+          .initializer("%L", anonymousClass)
+          .build(),
+      ).build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.util.ArrayList
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public val names: ArrayList<String> = object : ArrayList<String>(4) {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/315
+  @Test fun anonymousClassWithMultipleSuperTypes() {
+    val superclass = ClassName("com.squareup.wire", "Message")
+    val anonymousClass = TypeSpec.anonymousClassBuilder()
+      .superclass(superclass)
+      .addSuperinterface(Runnable::class)
+      .addFunction(
+        FunSpec.builder("run")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .addCode("/* code snippets */\n")
+          .build(),
+      ).build()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("NAME", Runnable::class)
+          .initializer("%L", anonymousClass)
+          .build(),
+      ).build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.wire.Message
+        |import java.lang.Runnable
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public val NAME: Runnable = object : Message(), Runnable {
+        |    public override fun run(): Unit {
+        |      /* code snippets */
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun anonymousClassWithoutSuperType() {
+    val anonymousClass = TypeSpec.anonymousClassBuilder().build()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("NAME", Any::class)
+          .initializer("%L", anonymousClass)
+          .build(),
+      ).build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Any
+        |
+        |public class Taco {
+        |  public val NAME: Any = object {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedParameters() {
+    val service = TypeSpec.classBuilder("Foo")
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addModifiers(KModifier.PUBLIC)
+          .addParameter("id", Long::class)
+          .addParameter(
+            ParameterSpec.builder("one", String::class)
+              .addAnnotation(ClassName(tacosPackage, "Ping"))
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("two", String::class)
+              .addAnnotation(ClassName(tacosPackage, "Ping"))
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("three", String::class)
+              .addAnnotation(
+                AnnotationSpec.builder(ClassName(tacosPackage, "Pong"))
+                  .addMember("%S", "pong")
+                  .build(),
+              )
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("four", String::class)
+              .addAnnotation(ClassName(tacosPackage, "Ping"))
+              .build(),
+          )
+          .addCode("/* code snippets */\n")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(service)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Long
+        |import kotlin.String
+        |
+        |public class Foo {
+        |  public constructor(
+        |    id: Long,
+        |    @Ping one: String,
+        |    @Ping two: String,
+        |    @Pong("pong") three: String,
+        |    @Ping four: String,
+        |  ) {
+        |    /* code snippets */
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /**
+   * We had a bug where annotations were preventing us from doing the right thing when resolving
+   * imports. https://github.com/square/javapoet/issues/422
+   */
+  @Test fun annotationsAndJavaLangTypes() {
+    val freeRange = ClassName("javax.annotation", "FreeRange")
+    val taco = TypeSpec.classBuilder("EthicalTaco")
+      .addProperty(
+        "meat",
+        String::class.asClassName()
+          .copy(annotations = listOf(AnnotationSpec.builder(freeRange).build())),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import javax.`annotation`.FreeRange
+        |import kotlin.String
+        |
+        |public class EthicalTaco {
+        |  public val meat: @FreeRange String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun retrofitStyleInterface() {
+    val observable = ClassName(tacosPackage, "Observable")
+    val fooBar = ClassName(tacosPackage, "FooBar")
+    val thing = ClassName(tacosPackage, "Thing")
+    val things = ClassName(tacosPackage, "Things")
+    val map = Map::class.asClassName()
+    val string = String::class.asClassName()
+    val headers = ClassName(tacosPackage, "Headers")
+    val post = ClassName(tacosPackage, "POST")
+    val body = ClassName(tacosPackage, "Body")
+    val queryMap = ClassName(tacosPackage, "QueryMap")
+    val header = ClassName(tacosPackage, "Header")
+    val service = TypeSpec.interfaceBuilder("Service")
+      .addFunction(
+        FunSpec.builder("fooBar")
+          .addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT)
+          .addAnnotation(
+            AnnotationSpec.builder(headers)
+              .addMember("%S", "Accept: application/json")
+              .addMember("%S", "User-Agent: foobar")
+              .build(),
+          )
+          .addAnnotation(
+            AnnotationSpec.builder(post)
+              .addMember("%S", "/foo/bar")
+              .build(),
+          )
+          .returns(observable.parameterizedBy(fooBar))
+          .addParameter(
+            ParameterSpec.builder("things", things.parameterizedBy(thing))
+              .addAnnotation(body)
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("query", map.parameterizedBy(string, string))
+              .addAnnotation(
+                AnnotationSpec.builder(queryMap)
+                  .addMember("encodeValues = %L", "false")
+                  .build(),
+              )
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("authorization", string)
+              .addAnnotation(
+                AnnotationSpec.builder(header)
+                  .addMember("%S", "Authorization")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(service)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.collections.Map
+        |
+        |public interface Service {
+        |  @Headers(
+        |    "Accept: application/json",
+        |    "User-Agent: foobar",
+        |  )
+        |  @POST("/foo/bar")
+        |  public fun fooBar(
+        |    @Body things: Things<Thing>,
+        |    @QueryMap(encodeValues = false) query: Map<String, String>,
+        |    @Header("Authorization") authorization: String,
+        |  ): Observable<FooBar>
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedProperty() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("thing", String::class, KModifier.PRIVATE)
+          .addAnnotation(
+            AnnotationSpec.builder(ClassName(tacosPackage, "JsonAdapter"))
+              .addMember("%T::class", ClassName(tacosPackage, "Foo"))
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  @JsonAdapter(Foo::class)
+        |  private val thing: String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedPropertyUseSiteTarget() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("thing", String::class, KModifier.PRIVATE)
+          .addAnnotation(
+            AnnotationSpec.builder(ClassName(tacosPackage, "JsonAdapter"))
+              .addMember("%T::class", ClassName(tacosPackage, "Foo"))
+              .useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  @field:JsonAdapter(Foo::class)
+        |  private val thing: String
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedClass() {
+    val someType = ClassName(tacosPackage, "SomeType")
+    val taco = TypeSpec.classBuilder("Foo")
+      .addAnnotation(
+        AnnotationSpec.builder(ClassName(tacosPackage, "Something"))
+          .addMember("%T.%N", someType, "PROPERTY")
+          .addMember("%L", 12)
+          .addMember("%S", "goodbye")
+          .build(),
+      )
+      .addModifiers(KModifier.PUBLIC)
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |@Something(
+        |  SomeType.PROPERTY,
+        |  12,
+        |  "goodbye",
+        |)
+        |public class Foo
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumWithSubclassing() {
+    val roshambo = TypeSpec.enumBuilder("Roshambo")
+      .addModifiers(KModifier.PUBLIC)
+      .addEnumConstant(
+        "ROCK",
+        TypeSpec.anonymousClassBuilder()
+          .addKdoc("Avalanche!\n")
+          .build(),
+      )
+      .addEnumConstant(
+        "PAPER",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%S", "flat")
+          .addFunction(
+            FunSpec.builder("toString")
+              .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.OVERRIDE)
+              .returns(String::class)
+              .addCode("return %S\n", "paper airplane!")
+              .build(),
+          )
+          .build(),
+      )
+      .addEnumConstant(
+        "SCISSORS",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%S", "peace sign")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("handPosition", String::class, KModifier.PRIVATE)
+          .initializer("handPosition")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("handPosition", String::class)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addCode("this(%S)\n", "fist")
+          .build(),
+      )
+      .build()
+    assertThat(toString(roshambo)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public enum class Roshambo(
+        |  private val handPosition: String,
+        |) {
+        |  /**
+        |   * Avalanche!
+        |   */
+        |  ROCK,
+        |  PAPER("flat") {
+        |    public override fun toString(): String = "paper airplane!"
+        |  },
+        |  SCISSORS("peace sign"),
+        |  ;
+        |
+        |  public constructor() {
+        |    this("fist")
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /** https://github.com/square/javapoet/issues/193  */
+  @Test fun enumsMayDefineAbstractFunctions() {
+    val roshambo = TypeSpec.enumBuilder("Tortilla")
+      .addModifiers(KModifier.PUBLIC)
+      .addEnumConstant(
+        "CORN",
+        TypeSpec.anonymousClassBuilder()
+          .addFunction(
+            FunSpec.builder("fold")
+              .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+              .build(),
+          )
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("fold")
+          .addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT)
+          .build(),
+      )
+      .build()
+    assertThat(toString(roshambo)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public enum class Tortilla {
+        |  CORN {
+        |    public override fun fold(): Unit {
+        |    }
+        |  },
+        |  ;
+        |
+        |  public abstract fun fold(): Unit
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumsMayHavePrivateConstructorVals() {
+    val enum = TypeSpec.enumBuilder("MyEnum")
+      .primaryConstructor(
+        FunSpec.constructorBuilder().addParameter("number", Int::class).build(),
+      )
+      .addProperty(
+        PropertySpec.builder("number", Int::class)
+          .addModifiers(PRIVATE)
+          .initializer("number")
+          .build(),
+      )
+      .build()
+    assertThat(toString(enum)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public enum class MyEnum(
+        |  private val number: Int,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classesMayHavePrivateConstructorPropertiesInTheirPrimaryConstructors() {
+    val myClass = TypeSpec.classBuilder("MyClass")
+      .primaryConstructor(
+        FunSpec.constructorBuilder().addParameter("number", Int::class).build(),
+      )
+      .addProperty(
+        PropertySpec.builder("number", Int::class)
+          .initializer("number")
+          .addModifiers(PRIVATE)
+          .build(),
+      )
+      .build()
+    assertThat(toString(myClass)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public class MyClass(
+        |  private val number: Int,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun sealedClassesMayDefineAbstractMembers() {
+    val sealedClass = TypeSpec.classBuilder("Sealed")
+      .addModifiers(KModifier.SEALED)
+      .addProperty(PropertySpec.builder("name", String::class).addModifiers(ABSTRACT).build())
+      .addFunction(FunSpec.builder("fold").addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT).build())
+      .build()
+    assertThat(toString(sealedClass)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public sealed class Sealed {
+        |  public abstract val name: String
+        |
+        |  public abstract fun fold(): Unit
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classesMayHaveVarargConstructorProperties() {
+    val variable = TypeSpec.classBuilder("Variable")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(ParameterSpec.builder("name", String::class, VARARG).build())
+          .build(),
+      )
+      .addProperty(PropertySpec.builder("name", String::class).initializer("name").build())
+      .build()
+    assertThat(toString(variable)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Variable(
+        |  public vararg val name: String,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /** https://github.com/square/kotlinpoet/issues/942  */
+  @Test fun noConstructorPropertiesWithCustomGetter() {
+    val taco = TypeSpec.classBuilder("ObservantTaco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(ParameterSpec.builder("contents", String::class).build())
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("contents", String::class).initializer("contents")
+          .getter(FunSpec.getterBuilder().addCode("println(%S)\nreturn field", "contents observed!").build())
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class ObservantTaco(
+        |  contents: String,
+        |) {
+        |  public val contents: String = contents
+        |    get() {
+        |      println("contents observed!")
+        |      return field
+        |    }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noConstructorPropertiesWithCustomSetter() {
+    val taco = TypeSpec.classBuilder("ObservantTaco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(ParameterSpec.builder("contents", String::class).build())
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("contents", String::class).initializer("contents")
+          .mutable()
+          .setter(
+            FunSpec.setterBuilder()
+              .addParameter("value", String::class)
+              .addCode("println(%S)\nfield = value", "contents changed!").build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class ObservantTaco(
+        |  contents: String,
+        |) {
+        |  public var contents: String = contents
+        |    set(`value`) {
+        |      println("contents changed!")
+        |      field = value
+        |    }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun onlyEnumsMayHaveEnumConstants() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.classBuilder("Roshambo")
+        .addEnumConstant("ROCK")
+        .build()
+    }
+  }
+
+  /** https://github.com/square/kotlinpoet/issues/621  */
+  @Test fun enumWithMembersButNoConstansts() {
+    val roshambo = TypeSpec.enumBuilder("RenderPassCreate")
+      .addType(TypeSpec.companionObjectBuilder().build())
+      .build()
+    assertThat(toString(roshambo)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public enum class RenderPassCreate {
+        |  ;
+        |  public companion object
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumWithMembersButNoConstructorCall() {
+    val roshambo = TypeSpec.enumBuilder("Roshambo")
+      .addEnumConstant(
+        "SPOCK",
+        TypeSpec.anonymousClassBuilder()
+          .addFunction(
+            FunSpec.builder("toString")
+              .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+              .returns(String::class)
+              .addStatement("return %S", "west side")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(roshambo)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public enum class Roshambo {
+        |  SPOCK {
+        |    public override fun toString(): String = "west side"
+        |  },
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  /** https://github.com/square/javapoet/issues/253  */
+  @Test fun enumWithAnnotatedValues() {
+    val roshambo = TypeSpec.enumBuilder("Roshambo")
+      .addModifiers(KModifier.PUBLIC)
+      .addEnumConstant(
+        "ROCK",
+        TypeSpec.anonymousClassBuilder()
+          .addAnnotation(java.lang.Deprecated::class)
+          .build(),
+      )
+      .addEnumConstant("PAPER")
+      .addEnumConstant("SCISSORS")
+      .build()
+    assertThat(toString(roshambo)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Deprecated
+        |
+        |public enum class Roshambo {
+        |  @Deprecated
+        |  ROCK,
+        |  PAPER,
+        |  SCISSORS,
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun funThrows() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addModifiers(KModifier.ABSTRACT)
+      .addFunction(
+        FunSpec.builder("throwOne")
+          .throws(IOException::class)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("throwTwo")
+          .throws(IOException::class.asClassName(), ClassName(tacosPackage, "SourCreamException"))
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("abstractThrow")
+          .addModifiers(KModifier.ABSTRACT)
+          .throws(IOException::class)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("nativeThrow")
+          .addModifiers(KModifier.EXTERNAL)
+          .throws(IOException::class)
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.IOException
+        |import kotlin.Unit
+        |import kotlin.jvm.Throws
+        |
+        |public abstract class Taco {
+        |  @Throws(IOException::class)
+        |  public fun throwOne(): Unit {
+        |  }
+        |
+        |  @Throws(
+        |    IOException::class,
+        |    SourCreamException::class,
+        |  )
+        |  public fun throwTwo(): Unit {
+        |  }
+        |
+        |  @Throws(IOException::class)
+        |  public abstract fun abstractThrow(): Unit
+        |
+        |  @Throws(IOException::class)
+        |  public external fun nativeThrow(): Unit
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun typeVariables() {
+    val t = TypeVariableName("T")
+    val p = TypeVariableName("P", Number::class)
+    val location = ClassName(tacosPackage, "Location")
+    val typeSpec = TypeSpec.classBuilder("Location")
+      .addTypeVariable(t)
+      .addTypeVariable(p)
+      .addSuperinterface(Comparable::class.asClassName().parameterizedBy(p))
+      .addProperty("label", t)
+      .addProperty("x", p)
+      .addProperty("y", p)
+      .addFunction(
+        FunSpec.builder("compareTo")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(Int::class)
+          .addParameter("p", p)
+          .addStatement("return 0")
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("of")
+          .addModifiers(KModifier.PUBLIC)
+          .addTypeVariable(t)
+          .addTypeVariable(p)
+          .returns(location.parameterizedBy(t, p))
+          .addParameter("label", t)
+          .addParameter("x", p)
+          .addParameter("y", p)
+          .addStatement("throw %T(%S)", UnsupportedOperationException::class, "TODO")
+          .build(),
+      )
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.UnsupportedOperationException
+        |import kotlin.Comparable
+        |import kotlin.Int
+        |import kotlin.Number
+        |
+        |public class Location<T, P : Number> : Comparable<P> {
+        |  public val label: T
+        |
+        |  public val x: P
+        |
+        |  public val y: P
+        |
+        |  public override fun compareTo(p: P): Int = 0
+        |
+        |  public fun <T, P : Number> of(
+        |    label: T,
+        |    x: P,
+        |    y: P,
+        |  ): Location<T, P> = throw UnsupportedOperationException("TODO")
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun typeVariableWithBounds() {
+    val a = AnnotationSpec.builder(ClassName("com.squareup.tacos", "A")).build()
+    val p = TypeVariableName("P", Number::class)
+    val q = TypeVariableName("Q", Number::class).copy(annotations = listOf(a)) as TypeVariableName
+    val typeSpec = TypeSpec.classBuilder("Location")
+      .addTypeVariable(p.copy(bounds = p.bounds + listOf(Comparable::class.asTypeName())))
+      .addTypeVariable(q.copy(bounds = q.bounds + listOf(Comparable::class.asTypeName())))
+      .addProperty("x", p)
+      .addProperty("y", q)
+      .primaryConstructor(FunSpec.constructorBuilder().build())
+      .superclass(Number::class)
+      .addSuperinterface(Comparable::class)
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Comparable
+        |import kotlin.Number
+        |
+        |public class Location<P, Q>() : Number(), Comparable where P : Number, P : Comparable, Q : Number, Q
+        |    : Comparable {
+        |  public val x: P
+        |
+        |  public val y: @A Q
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classImplementsExtends() {
+    val taco = ClassName(tacosPackage, "Taco")
+    val food = ClassName("com.squareup.tacos", "Food")
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addModifiers(KModifier.ABSTRACT)
+      .superclass(AbstractSet::class.asClassName().parameterizedBy(food))
+      .addSuperinterface(Serializable::class)
+      .addSuperinterface(Comparable::class.asClassName().parameterizedBy(taco))
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import java.util.AbstractSet
+        |import kotlin.Comparable
+        |
+        |public abstract class Taco : AbstractSet<Food>(), Serializable, Comparable<Taco>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classImplementsExtendsSameName() {
+    val javapoetTaco = ClassName(tacosPackage, "Taco")
+    val tacoBellTaco = ClassName("com.taco.bell", "Taco")
+    val fishTaco = ClassName("org.fish.taco", "Taco")
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .superclass(fishTaco)
+      .addSuperinterface(Comparable::class.asClassName().parameterizedBy(javapoetTaco))
+      .addSuperinterface(tacoBellTaco)
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Comparable
+        |
+        |public class Taco : org.fish.taco.Taco(), Comparable<Taco>, com.taco.bell.Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classImplementsInnerClass() {
+    val outer = ClassName(tacosPackage, "Outer")
+    val inner = outer.nestedClass("Inner")
+    val callable = Callable::class.asClassName()
+    val typeSpec = TypeSpec.classBuilder("Outer")
+      .superclass(callable.parameterizedBy(inner))
+      .addType(
+        TypeSpec.classBuilder("Inner")
+          .addModifiers(KModifier.INNER)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.util.concurrent.Callable
+        |
+        |public class Outer : Callable<Outer.Inner>() {
+        |  public inner class Inner
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumImplements() {
+    val typeSpec = TypeSpec.enumBuilder("Food")
+      .addSuperinterface(Serializable::class)
+      .addSuperinterface(Cloneable::class)
+      .addEnumConstant("LEAN_GROUND_BEEF")
+      .addEnumConstant("SHREDDED_CHEESE")
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import kotlin.Cloneable
+        |
+        |public enum class Food : Serializable, Cloneable {
+        |  LEAN_GROUND_BEEF,
+        |  SHREDDED_CHEESE,
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun enumWithConstructorsAndKeywords() {
+    val primaryConstructor = FunSpec.constructorBuilder()
+      .addParameter("value", Int::class)
+      .build()
+    val typeSpec = TypeSpec.enumBuilder("Sort")
+      .primaryConstructor(primaryConstructor)
+      .addEnumConstant(
+        "open",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 0)
+          .build(),
+      )
+      .addEnumConstant(
+        "closed",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 1)
+          .build(),
+      )
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public enum class Sort(
+        |  `value`: Int,
+        |) {
+        |  `open`(0),
+        |  closed(1),
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun interfaceExtends() {
+    val taco = ClassName(tacosPackage, "Taco")
+    val typeSpec = TypeSpec.interfaceBuilder("Taco")
+      .addSuperinterface(Serializable::class)
+      .addSuperinterface(Comparable::class.asClassName().parameterizedBy(taco))
+      .build()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import kotlin.Comparable
+        |
+        |public interface Taco : Serializable, Comparable<Taco>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun funInterface() {
+    val taco = ClassName(tacosPackage, "Taco")
+    val typeSpec = TypeSpec.funInterfaceBuilder(taco)
+      .addFunction(
+        FunSpec.builder("sam")
+          .addModifiers(ABSTRACT)
+          .build(),
+      )
+      .addFunction(FunSpec.builder("notSam").build())
+      .build()
+    assertThat(typeSpec.isFunctionalInterface).isTrue()
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public fun interface Taco {
+        |  public fun sam(): Unit
+        |
+        |  public fun notSam(): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun funInterface_empty_shouldError() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.funInterfaceBuilder("Taco")
+        .build()
+    }.hasMessageThat()
+      .contains("Functional interfaces must have exactly one abstract function. Contained 0")
+  }
+
+  @Test fun funInterface_multipleAbstract_shouldError() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.funInterfaceBuilder("Taco")
+        .addFunction(
+          FunSpec.builder("fun1")
+            .addModifiers(ABSTRACT)
+            .build(),
+        )
+        .addFunction(
+          FunSpec.builder("fun2")
+            .addModifiers(ABSTRACT)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat()
+      .contains("Functional interfaces must have exactly one abstract function. Contained 2")
+  }
+
+  @Test fun nestedClasses() {
+    val taco = ClassName(tacosPackage, "Combo", "Taco")
+    val topping = ClassName(tacosPackage, "Combo", "Taco", "Topping")
+    val chips = ClassName(tacosPackage, "Combo", "Chips")
+    val sauce = ClassName(tacosPackage, "Combo", "Sauce")
+    val typeSpec = TypeSpec.classBuilder("Combo")
+      .addProperty("taco", taco)
+      .addProperty("chips", chips)
+      .addType(
+        TypeSpec.classBuilder(taco.simpleName)
+          .addProperty("toppings", List::class.asClassName().parameterizedBy(topping))
+          .addProperty("sauce", sauce)
+          .addType(
+            TypeSpec.enumBuilder(topping.simpleName)
+              .addEnumConstant("SHREDDED_CHEESE")
+              .addEnumConstant("LEAN_GROUND_BEEF")
+              .build(),
+          )
+          .build(),
+      )
+      .addType(
+        TypeSpec.classBuilder(chips.simpleName)
+          .addProperty("topping", topping)
+          .addProperty("dippingSauce", sauce)
+          .build(),
+      )
+      .addType(
+        TypeSpec.enumBuilder(sauce.simpleName)
+          .addEnumConstant("SOUR_CREAM")
+          .addEnumConstant("SALSA")
+          .addEnumConstant("QUESO")
+          .addEnumConstant("MILD")
+          .addEnumConstant("FIRE")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.collections.List
+        |
+        |public class Combo {
+        |  public val taco: Taco
+        |
+        |  public val chips: Chips
+        |
+        |  public class Taco {
+        |    public val toppings: List<Topping>
+        |
+        |    public val sauce: Sauce
+        |
+        |    public enum class Topping {
+        |      SHREDDED_CHEESE,
+        |      LEAN_GROUND_BEEF,
+        |    }
+        |  }
+        |
+        |  public class Chips {
+        |    public val topping: Taco.Topping
+        |
+        |    public val dippingSauce: Sauce
+        |  }
+        |
+        |  public enum class Sauce {
+        |    SOUR_CREAM,
+        |    SALSA,
+        |    QUESO,
+        |    MILD,
+        |    FIRE,
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotation() {
+    val annotation = TypeSpec.annotationBuilder("MyAnnotation")
+      .addModifiers(KModifier.PUBLIC)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("test", Int::class)
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("test", Int::class)
+          .initializer("test")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(annotation)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public annotation class MyAnnotation(
+        |  public val test: Int,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotationWithNestedTypes() {
+    val annotationName = ClassName(tacosPackage, "TacoDelivery")
+    val kindName = annotationName.nestedClass("Kind")
+    val annotation = TypeSpec.annotationBuilder(annotationName)
+      .addModifiers(PUBLIC)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("kind", kindName)
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("quantity", Int::class)
+              .defaultValue("QUANTITY_DEFAULT")
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("kind", kindName)
+          .initializer("kind")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("quantity", Int::class)
+          .initializer("quantity")
+          .build(),
+      )
+      .addType(
+        TypeSpec.enumBuilder("Kind")
+          .addEnumConstant("SOFT")
+          .addEnumConstant("HARD")
+          .build(),
+      )
+      .addType(
+        TypeSpec.companionObjectBuilder()
+          .addProperty(
+            PropertySpec
+              .builder("QUANTITY_DEFAULT", Int::class, KModifier.CONST)
+              .initializer("%L", 10_000)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(annotation)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public annotation class TacoDelivery(
+        |  public val kind: Kind,
+        |  public val quantity: Int = QUANTITY_DEFAULT,
+        |) {
+        |  public enum class Kind {
+        |    SOFT,
+        |    HARD,
+        |  }
+        |
+        |  public companion object {
+        |    public const val QUANTITY_DEFAULT: Int = 10_000
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Ignore @Test
+  fun innerAnnotationInAnnotationDeclaration() {
+    val bar = TypeSpec.annotationBuilder("Bar")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("value", java.lang.Deprecated::class)
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("value", java.lang.Deprecated::class)
+          .initializer("value")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(bar)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Deprecated
+        |
+        |annotation class Bar() {
+        |  fun value(): Deprecated default @Deprecated
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun interfaceWithProperties() {
+    val taco = TypeSpec.interfaceBuilder("Taco")
+      .addProperty("v", Int::class)
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public interface Taco {
+        |  public val v: Int
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun expectClass() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addFunction(
+        FunSpec.builder("test")
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public fun test(): kotlin.Unit
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nestedExpectCompanionObjectWithFunction() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.companionObjectBuilder()
+          .addFunction(
+            FunSpec.builder("test")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public companion object {
+      |    public fun test(): kotlin.Unit
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nestedExpectClassWithFunction() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.classBuilder("ClassB")
+          .addFunction(
+            FunSpec.builder("test")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public class ClassB {
+      |    public fun test(): kotlin.Unit
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun deeplyNestedExpectClassWithFunction() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.classBuilder("ClassB")
+          .addType(
+            TypeSpec.classBuilder("ClassC")
+              .addFunction(
+                FunSpec.builder("test")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public class ClassB {
+      |    public class ClassC {
+      |      public fun test(): kotlin.Unit
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun veryDeeplyNestedExpectClassWithFunction() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.classBuilder("ClassB")
+          .addType(
+            TypeSpec.classBuilder("ClassC")
+              .addType(
+                TypeSpec.classBuilder("ClassD")
+                  .addFunction(
+                    FunSpec.builder("test")
+                      .build(),
+                  )
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public class ClassB {
+      |    public class ClassC {
+      |      public class ClassD {
+      |        public fun test(): kotlin.Unit
+      |      }
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun deeplyNestedExpectClassWithConstructor() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.classBuilder("ClassB")
+          .addType(
+            TypeSpec.classBuilder("ClassC")
+              .addFunction(
+                FunSpec.constructorBuilder()
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public class ClassB {
+      |    public class ClassC {
+      |      public constructor()
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun veryDeeplyNestedExpectClassWithConstructor() {
+    val classA = TypeSpec.expectClassBuilder("ClassA")
+      .addType(
+        TypeSpec.classBuilder("ClassB")
+          .addType(
+            TypeSpec.classBuilder("ClassC")
+              .addType(
+                TypeSpec.classBuilder("ClassD")
+                  .addFunction(
+                    FunSpec.constructorBuilder()
+                      .build(),
+                  )
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(classA.toString()).isEqualTo(
+      """
+      |public expect class ClassA {
+      |  public class ClassB {
+      |    public class ClassC {
+      |      public class ClassD {
+      |        public constructor()
+      |      }
+      |    }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun interfaceWithMethods() {
+    val taco = TypeSpec.interfaceBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("aMethod")
+          .addModifiers(KModifier.ABSTRACT)
+          .build(),
+      )
+      .addFunction(FunSpec.builder("aDefaultMethod").build())
+      .addFunction(
+        FunSpec.builder("aPrivateMethod")
+          .addModifiers(KModifier.PRIVATE)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public interface Taco {
+        |  public fun aMethod(): Unit
+        |
+        |  public fun aDefaultMethod(): Unit {
+        |  }
+        |
+        |  private fun aPrivateMethod(): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun referencedAndDeclaredSimpleNamesConflict() {
+    val internalTop = PropertySpec.builder(
+      "internalTop",
+      ClassName(tacosPackage, "Top"),
+    ).build()
+    val internalBottom = PropertySpec.builder(
+      "internalBottom",
+      ClassName(tacosPackage, "Top", "Middle", "Bottom"),
+    ).build()
+    val externalTop = PropertySpec.builder(
+      "externalTop",
+      ClassName(donutsPackage, "Top"),
+    ).build()
+    val externalBottom = PropertySpec.builder(
+      "externalBottom",
+      ClassName(donutsPackage, "Bottom"),
+    ).build()
+    val top = TypeSpec.classBuilder("Top")
+      .addProperty(internalTop)
+      .addProperty(internalBottom)
+      .addProperty(externalTop)
+      .addProperty(externalBottom)
+      .addType(
+        TypeSpec.classBuilder("Middle")
+          .addProperty(internalTop)
+          .addProperty(internalBottom)
+          .addProperty(externalTop)
+          .addProperty(externalBottom)
+          .addType(
+            TypeSpec.classBuilder("Bottom")
+              .addProperty(internalTop)
+              .addProperty(internalBottom)
+              .addProperty(externalTop)
+              .addProperty(externalBottom)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(top)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.donuts.Bottom
+        |
+        |public class Top {
+        |  public val internalTop: Top
+        |
+        |  public val internalBottom: Middle.Bottom
+        |
+        |  public val externalTop: com.squareup.donuts.Top
+        |
+        |  public val externalBottom: Bottom
+        |
+        |  public class Middle {
+        |    public val internalTop: Top
+        |
+        |    public val internalBottom: Bottom
+        |
+        |    public val externalTop: com.squareup.donuts.Top
+        |
+        |    public val externalBottom: com.squareup.donuts.Bottom
+        |
+        |    public class Bottom {
+        |      public val internalTop: Top
+        |
+        |      public val internalBottom: Bottom
+        |
+        |      public val externalTop: com.squareup.donuts.Top
+        |
+        |      public val externalBottom: com.squareup.donuts.Bottom
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun simpleNamesConflictInThisAndOtherPackage() {
+    val internalOther = PropertySpec.builder(
+      "internalOther",
+      ClassName(tacosPackage, "Other"),
+    ).build()
+    val externalOther = PropertySpec.builder(
+      "externalOther",
+      ClassName(donutsPackage, "Other"),
+    ).build()
+    val gen = TypeSpec.classBuilder("Gen")
+      .addProperty(internalOther)
+      .addProperty(externalOther)
+      .build()
+    assertThat(toString(gen)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Gen {
+        |  public val internalOther: Other
+        |
+        |  public val externalOther: com.squareup.donuts.Other
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun intersectionType() {
+    val typeVariable = TypeVariableName("T", Comparator::class, Serializable::class)
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("getComparator")
+          .addTypeVariable(typeVariable)
+          .returns(typeVariable)
+          .addStatement("return null")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import java.util.Comparator
+        |
+        |public class Taco {
+        |  public fun <T> getComparator(): T where T : Comparator, T : Serializable = null
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun primitiveArrayType() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty("ints", IntArray::class)
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.IntArray
+        |
+        |public class Taco {
+        |  public val ints: IntArray
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun kdoc() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addKdoc("A hard or soft tortilla, loosely folded and filled with whatever\n")
+      .addKdoc("[random][%T] tex-mex stuff we could find in the pantry\n", Random::class)
+      .addKdoc(CodeBlock.of("and some [%T] cheese.\n", String::class))
+      .addProperty(
+        PropertySpec.builder("soft", Boolean::class)
+          .addKdoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("refold")
+          .addKdoc(
+            "Folds the back of this taco to reduce sauce leakage.\n" +
+              "\n" +
+              "For [%T#KOREAN], the front may also be folded.\n",
+            Locale::class,
+          )
+          .addParameter("locale", Locale::class)
+          .build(),
+      )
+      .build()
+    // Mentioning a type in KDoc will not cause an import to be added (java.util.Random here), but
+    // the short name will be used if it's already imported (java.util.Locale here).
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.util.Locale
+        |import kotlin.Boolean
+        |import kotlin.Unit
+        |
+        |/**
+        | * A hard or soft tortilla, loosely folded and filled with whatever
+        | * [random][java.util.Random] tex-mex stuff we could find in the pantry
+        | * and some [kotlin.String] cheese.
+        | */
+        |public class Taco {
+        |  /**
+        |   * True for a soft flour tortilla; false for a crunchy corn tortilla.
+        |   */
+        |  public val soft: Boolean
+        |
+        |  /**
+        |   * Folds the back of this taco to reduce sauce leakage.
+        |   *
+        |   * For [Locale#KOREAN], the front may also be folded.
+        |   */
+        |  public fun refold(locale: Locale): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun kdocWithParameters() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addKdoc("A hard or soft tortilla, loosely folded and filled with whatever\n")
+      .addKdoc("[random][%T] tex-mex stuff we could find in the pantry\n", Random::class)
+      .addKdoc(CodeBlock.of("and some [%T] cheese.\n", String::class))
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("temperature", Double::class)
+              .addKdoc(
+                CodeBlock.of(
+                  "%L",
+                  """
+                    |Taco temperature. Can be as cold as the famous ice tacos from
+                    |the Andes, or hot with lava-like cheese from the depths of
+                    |the Ninth Circle.
+                    |
+                  """.trimMargin(),
+                ),
+              )
+              .build(),
+          )
+          .addParameter("soft", Boolean::class)
+          .addParameter(
+            ParameterSpec.builder("mild", Boolean::class)
+              .addKdoc(CodeBlock.of("%L", "Whether the taco is mild (ew) or crunchy (ye).\n"))
+              .build(),
+          )
+          .addParameter("nodoc", Int::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("soft", Boolean::class)
+          .addKdoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+          .initializer("soft")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("mild", Boolean::class)
+          .addKdoc("No one likes mild tacos.")
+          .initializer("mild")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("nodoc", Int::class, KModifier.PRIVATE)
+          .initializer("nodoc")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Boolean
+        |import kotlin.Double
+        |import kotlin.Int
+        |
+        |/**
+        | * A hard or soft tortilla, loosely folded and filled with whatever
+        | * [random][java.util.Random] tex-mex stuff we could find in the pantry
+        | * and some [kotlin.String] cheese.
+        | *
+        | * @param mild Whether the taco is mild (ew) or crunchy (ye).
+        | */
+        |public class Taco(
+        |  /**
+        |   * Taco temperature. Can be as cold as the famous ice tacos from
+        |   * the Andes, or hot with lava-like cheese from the depths of
+        |   * the Ninth Circle.
+        |   */
+        |  temperature: Double,
+        |  /**
+        |   * True for a soft flour tortilla; false for a crunchy corn tortilla.
+        |   */
+        |  public val soft: Boolean,
+        |  /**
+        |   * No one likes mild tacos.
+        |   */
+        |  public val mild: Boolean,
+        |  private val nodoc: Int,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotationsInAnnotations() {
+    val beef = ClassName(tacosPackage, "Beef")
+    val chicken = ClassName(tacosPackage, "Chicken")
+    val option = ClassName(tacosPackage, "Option")
+    val mealDeal = ClassName(tacosPackage, "MealDeal")
+    val menu = TypeSpec.classBuilder("Menu")
+      .addAnnotation(
+        AnnotationSpec.builder(mealDeal)
+          .addMember("%L = %L", "price", 500)
+          .addMember(
+            "%L = [%L, %L]",
+            "options",
+            AnnotationSpec.builder(option)
+              .addMember("%S", "taco")
+              .addMember("%T::class", beef)
+              .build(),
+            AnnotationSpec.builder(option)
+              .addMember("%S", "quesadilla")
+              .addMember("%T::class", chicken)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(menu)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |@MealDeal(
+        |  price = 500,
+        |  options = [Option("taco", Beef::class), Option("quesadilla", Chicken::class)],
+        |)
+        |public class Menu
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun varargs() {
+    val taqueria = TypeSpec.classBuilder("Taqueria")
+      .addFunction(
+        FunSpec.builder("prepare")
+          .addParameter("workers", Int::class)
+          .addParameter("jobs", Runnable::class.asClassName(), VARARG)
+          .build(),
+      )
+      .build()
+    assertThat(toString(taqueria)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Runnable
+        |import kotlin.Int
+        |import kotlin.Unit
+        |
+        |public class Taqueria {
+        |  public fun prepare(workers: Int, vararg jobs: Runnable): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun varargsNotLast() {
+    val taqueria = TypeSpec.classBuilder("Taqueria")
+      .addFunction(
+        FunSpec.builder("prepare")
+          .addParameter("workers", Int::class)
+          .addParameter("jobs", Runnable::class.asClassName(), VARARG)
+          .addParameter("start", Boolean::class.asClassName())
+          .build(),
+      )
+      .build()
+    assertThat(toString(taqueria)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Runnable
+        |import kotlin.Boolean
+        |import kotlin.Int
+        |import kotlin.Unit
+        |
+        |public class Taqueria {
+        |  public fun prepare(
+        |    workers: Int,
+        |    vararg jobs: Runnable,
+        |    start: Boolean,
+        |  ): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun codeBlocks() {
+    val ifBlock = CodeBlock.builder()
+      .beginControlFlow("if (a != b)")
+      .addStatement("return i")
+      .endControlFlow()
+      .build()
+    val funBody = CodeBlock.builder()
+      .addStatement("val size = %T.min(listA.size, listB.size)", Math::class)
+      .beginControlFlow("for (i in 0 until size)")
+      .addStatement("val %N = %N[i]", "a", "listA")
+      .addStatement("val %N = %N[i]", "b", "listB")
+      .add("%L", ifBlock)
+      .endControlFlow()
+      .addStatement("return size")
+      .build()
+    val propertyBlock = CodeBlock.builder()
+      .add("%T.<%T, %T>builder()", ImmutableMap::class, String::class, String::class)
+      .add("\n.add(%S, %S)", '\'', "&#39;")
+      .add("\n.add(%S, %S)", '&', "&amp;")
+      .add("\n.add(%S, %S)", '<', "&lt;")
+      .add("\n.add(%S, %S)", '>', "&gt;")
+      .add("\n.build()")
+      .build()
+    val escapeHtml = PropertySpec.builder(
+      "ESCAPE_HTML",
+      Map::class.parameterizedBy(String::class, String::class),
+    )
+      .addModifiers(KModifier.PRIVATE)
+      .initializer(propertyBlock)
+      .build()
+    val util = TypeSpec.classBuilder("Util")
+      .addProperty(escapeHtml)
+      .addFunction(
+        FunSpec.builder("commonPrefixLength")
+          .returns(Int::class)
+          .addParameter("listA", List::class.parameterizedBy(String::class))
+          .addParameter("listB", List::class.parameterizedBy(String::class))
+          .addCode(funBody)
+          .build(),
+      )
+      .build()
+    assertThat(toString(util)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.google.common.collect.ImmutableMap
+        |import java.lang.Math
+        |import kotlin.Int
+        |import kotlin.String
+        |import kotlin.collections.List
+        |import kotlin.collections.Map
+        |
+        |public class Util {
+        |  private val ESCAPE_HTML: Map<String, String> = ImmutableMap.<String, String>builder()
+        |      .add("'", "&#39;")
+        |      .add("&", "&amp;")
+        |      .add("<", "&lt;")
+        |      .add(">", "&gt;")
+        |      .build()
+        |
+        |  public fun commonPrefixLength(listA: List<String>, listB: List<String>): Int {
+        |    val size = Math.min(listA.size, listB.size)
+        |    for (i in 0 until size) {
+        |      val a = listA[i]
+        |      val b = listB[i]
+        |      if (a != b) {
+        |        return i
+        |      }
+        |    }
+        |    return size
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun indexedElseIf() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("choices")
+          .beginControlFlow("if (%1L != null || %1L == %2L)", "taco", "otherTaco")
+          .addStatement("%T.out.println(%S)", System::class, "only one taco? NOO!")
+          .nextControlFlow("else if (%1L.%3L && %2L.%3L)", "taco", "otherTaco", "isSupreme()")
+          .addStatement("%T.out.println(%S)", System::class, "taco heaven")
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.System
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun choices(): Unit {
+        |    if (taco != null || taco == otherTaco) {
+        |      System.out.println("only one taco? NOO!")
+        |    } else if (taco.isSupreme() && otherTaco.isSupreme()) {
+        |      System.out.println("taco heaven")
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun elseIf() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("choices")
+          .beginControlFlow("if (5 < 4) ")
+          .addStatement("%T.out.println(%S)", System::class, "wat")
+          .nextControlFlow("else if (5 < 6)")
+          .addStatement("%T.out.println(%S)", System::class, "hello")
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.System
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun choices(): Unit {
+        |    if (5 < 4)  {
+        |      System.out.println("wat")
+        |    } else if (5 < 6) {
+        |      System.out.println("hello")
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineIndent() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("inlineIndent")
+          .addCode("if (3 < 4) {\n⇥%T.out.println(%S);\n⇤}\n", System::class, "hello")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.System
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun inlineIndent(): Unit {
+        |    if (3 < 4) {
+        |      System.out.println("hello");
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun defaultModifiersForMemberInterfacesAndEnums() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addType(
+        TypeSpec.classBuilder("Meat")
+          .build(),
+      )
+      .addType(
+        TypeSpec.interfaceBuilder("Tortilla")
+          .build(),
+      )
+      .addType(
+        TypeSpec.enumBuilder("Topping")
+          .addEnumConstant("SALSA")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Taco {
+        |  public class Meat
+        |
+        |  public interface Tortilla
+        |
+        |  public enum class Topping {
+        |    SALSA,
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun membersOrdering() {
+    // Hand out names in reverse-alphabetical order to defend against unexpected sorting.
+    val taco = TypeSpec.classBuilder("Members")
+      .addType(TypeSpec.classBuilder("Z").build())
+      .addType(TypeSpec.classBuilder("Y").build())
+      .addProperty("W", String::class)
+      .addProperty("U", String::class)
+      .addFunction(FunSpec.builder("T").build())
+      .addFunction(FunSpec.builder("S").build())
+      .addFunction(FunSpec.builder("R").build())
+      .addFunction(FunSpec.builder("Q").build())
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addParameter("p", Int::class)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addParameter("o", Long::class)
+          .build(),
+      )
+      .build()
+    // Static properties, instance properties, constructors, functions, classes.
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.Long
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public class Members {
+        |  public val W: String
+        |
+        |  public val U: String
+        |
+        |  public constructor(p: Int)
+        |
+        |  public constructor(o: Long)
+        |
+        |  public fun T(): Unit {
+        |  }
+        |
+        |  public fun S(): Unit {
+        |  }
+        |
+        |  public fun R(): Unit {
+        |  }
+        |
+        |  public fun Q(): Unit {
+        |  }
+        |
+        |  public class Z
+        |
+        |  public class Y
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nativeFunctions() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("nativeInt")
+          .addModifiers(KModifier.EXTERNAL)
+          .returns(Int::class)
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public class Taco {
+        |  public external fun nativeInt(): Int
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nullStringLiteral() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("NULL", String::class)
+          .initializer("%S", null)
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public val NULL: String = null
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotationToString() {
+    val annotation = AnnotationSpec.builder(SuppressWarnings::class)
+      .addMember("%S", "unused")
+      .build()
+    assertThat(annotation.toString()).isEqualTo("@java.lang.SuppressWarnings(\"unused\")")
+  }
+
+  @Test fun codeBlockToString() {
+    val codeBlock = CodeBlock.builder()
+      .addStatement("%T %N = %S.substring(0, 3)", String::class, "s", "taco")
+      .build()
+    assertThat(codeBlock.toString()).isEqualTo("kotlin.String s = \"taco\".substring(0, 3)\n")
+  }
+
+  @Test fun propertyToString() {
+    val property = PropertySpec.builder("s", String::class)
+      .initializer("%S.substring(0, 3)", "taco")
+      .build()
+    assertThat(property.toString())
+      .isEqualTo("val s: kotlin.String = \"taco\".substring(0, 3)\n")
+  }
+
+  @Test fun functionToString() {
+    val funSpec = FunSpec.builder("toString")
+      .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+      .returns(String::class)
+      .addStatement("return %S", "taco")
+      .build()
+    assertThat(funSpec.toString())
+      .isEqualTo("public override fun toString(): kotlin.String = \"taco\"\n")
+  }
+
+  @Test fun constructorToString() {
+    val constructor = FunSpec.constructorBuilder()
+      .addModifiers(KModifier.PUBLIC)
+      .addParameter("taco", ClassName(tacosPackage, "Taco"))
+      .addStatement("this.%N = %N", "taco", "taco")
+      .build()
+    assertThat(constructor.toString()).isEqualTo(
+      "" +
+        "public constructor(taco: com.squareup.tacos.Taco) {\n" +
+        "  this.taco = taco\n" +
+        "}\n",
+    )
+  }
+
+  @Test fun parameterToString() {
+    val parameter = ParameterSpec.builder("taco", ClassName(tacosPackage, "Taco"))
+      .addModifiers(KModifier.CROSSINLINE)
+      .addAnnotation(ClassName("javax.annotation", "Nullable"))
+      .build()
+    assertThat(parameter.toString())
+      .isEqualTo("@javax.`annotation`.Nullable crossinline taco: com.squareup.tacos.Taco")
+  }
+
+  @Test fun classToString() {
+    val type = TypeSpec.classBuilder("Taco")
+      .build()
+    assertThat(type.toString()).isEqualTo(
+      "" +
+        "public class Taco\n",
+    )
+  }
+
+  @Test fun anonymousClassToString() {
+    val type = TypeSpec.anonymousClassBuilder()
+      .addSuperinterface(Runnable::class)
+      .addFunction(
+        FunSpec.builder("run")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .build(),
+      )
+      .build()
+    assertThat(type.toString()).isEqualTo(
+      """
+        |object : java.lang.Runnable {
+        |  public override fun run(): kotlin.Unit {
+        |  }
+        |}
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun interfaceClassToString() {
+    val type = TypeSpec.interfaceBuilder("Taco")
+      .build()
+    assertThat(type.toString()).isEqualTo(
+      """
+        |public interface Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotationDeclarationToString() {
+    val type = TypeSpec.annotationBuilder("Taco")
+      .build()
+    assertThat(type.toString()).isEqualTo(
+      """
+        |public annotation class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  private fun toString(typeSpec: TypeSpec): String {
+    return FileSpec.get(tacosPackage, typeSpec).toString()
+  }
+
+  @Test fun multilineStatement() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("toString")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(String::class)
+          .addStatement(
+            "val result = %S\n+ %S\n+ %S\n+ %S\n+ %S",
+            "Taco(",
+            "beef,",
+            "lettuce,",
+            "cheese",
+            ")",
+          )
+          .addStatement("return result")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public override fun toString(): String {
+        |    val result = "Taco("
+        |        + "beef,"
+        |        + "lettuce,"
+        |        + "cheese"
+        |        + ")"
+        |    return result
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multilineStatementWithAnonymousClass() {
+    val stringComparator = Comparator::class.parameterizedBy(String::class)
+    val listOfString = List::class.parameterizedBy(String::class)
+    val prefixComparator = TypeSpec.anonymousClassBuilder()
+      .addSuperinterface(stringComparator)
+      .addFunction(
+        FunSpec.builder("compare")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(Int::class)
+          .addParameter("a", String::class)
+          .addParameter("b", String::class)
+          .addComment("Prefix the strings and compare them")
+          .addStatement("return a.substring(0, length)\n" + ".compareTo(b.substring(0, length))")
+          .build(),
+      )
+      .build()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("comparePrefix")
+          .returns(stringComparator)
+          .addParameter("length", Int::class)
+          .addComment("Return a new comparator for the target length.")
+          .addStatement("return %L", prefixComparator)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("sortPrefix")
+          .addParameter("list", listOfString)
+          .addParameter("length", Int::class)
+          .addStatement("%T.sort(\nlist,\n%L)", Collections::class, prefixComparator)
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.util.Collections
+        |import java.util.Comparator
+        |import kotlin.Int
+        |import kotlin.String
+        |import kotlin.Unit
+        |import kotlin.collections.List
+        |
+        |public class Taco {
+        |  public fun comparePrefix(length: Int): Comparator<String> {
+        |    // Return a new comparator for the target length.
+        |    return object : Comparator<String> {
+        |      public override fun compare(a: String, b: String): Int {
+        |        // Prefix the strings and compare them
+        |        return a.substring(0, length)
+        |            .compareTo(b.substring(0, length))
+        |      }
+        |    }
+        |  }
+        |
+        |  public fun sortPrefix(list: List<String>, length: Int): Unit {
+        |    Collections.sort(
+        |        list,
+        |        object : Comparator<String> {
+        |          public override fun compare(a: String, b: String): Int {
+        |            // Prefix the strings and compare them
+        |            return a.substring(0, length)
+        |                .compareTo(b.substring(0, length))
+        |          }
+        |        })
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multilineStrings() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("toppings", String::class)
+          .initializer("%S", "shell\nbeef\nlettuce\ncheese\n")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public val toppings: String = ${"\"\"\""}
+        |      |shell
+        |      |beef
+        |      |lettuce
+        |      |cheese
+        |      |${"\"\"\""}.trimMargin()
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleAnnotationAddition() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addAnnotations(
+        listOf(
+          AnnotationSpec.builder(SuppressWarnings::class)
+            .addMember("%S", "unchecked")
+            .build(),
+          AnnotationSpec.builder(Deprecated::class).build(),
+        ),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Deprecated
+        |import java.lang.SuppressWarnings
+        |
+        |@SuppressWarnings("unchecked")
+        |@Deprecated
+        |public class Taco
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multiplePropertyAddition() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperties(
+        listOf(
+          PropertySpec.builder("ANSWER", Int::class, KModifier.CONST).build(),
+          PropertySpec.builder("price", BigDecimal::class, PRIVATE).build(),
+        ),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.math.BigDecimal
+        |import kotlin.Int
+        |
+        |public class Taco {
+        |  public const val ANSWER: Int
+        |
+        |  private val price: BigDecimal
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleFunctionAddition() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunctions(
+        listOf(
+          FunSpec.builder("getAnswer")
+            .addModifiers(PUBLIC)
+            .returns(Int::class)
+            .addStatement("return %L", 42)
+            .build(),
+          FunSpec.builder("getRandomQuantity")
+            .addModifiers(PUBLIC)
+            .returns(Int::class)
+            .addKdoc("chosen by fair dice roll ;)\n")
+            .addStatement("return %L", 4)
+            .build(),
+        ),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public class Taco {
+        |  public fun getAnswer(): Int = 42
+        |
+        |  /**
+        |   * chosen by fair dice roll ;)
+        |   */
+        |  public fun getRandomQuantity(): Int = 4
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleSuperinterfaceAddition() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addSuperinterfaces(
+        listOf(
+          Serializable::class.asTypeName(),
+          EventListener::class.asTypeName(),
+        ),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import java.io.Serializable
+        |import java.util.EventListener
+        |
+        |public class Taco : Serializable, EventListener
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleTypeVariableAddition() {
+    val location = TypeSpec.classBuilder("Location")
+      .addTypeVariables(
+        listOf(
+          TypeVariableName("T"),
+          TypeVariableName("P", Number::class),
+        ),
+      )
+      .build()
+    assertThat(toString(location)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Number
+        |
+        |public class Location<T, P : Number>
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleTypeAddition() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addTypes(
+        listOf(
+          TypeSpec.classBuilder("Topping").build(),
+          TypeSpec.classBuilder("Sauce").build(),
+        ),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Taco {
+        |  public class Topping
+        |
+        |  public class Sauce
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun tryCatch() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("addTopping")
+          .addParameter("topping", ClassName("com.squareup.tacos", "Topping"))
+          .beginControlFlow("try")
+          .addCode("/* do something tricky with the topping */\n")
+          .nextControlFlow(
+            "catch (e: %T)",
+            ClassName("com.squareup.tacos", "IllegalToppingException"),
+          )
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun addTopping(topping: Topping): Unit {
+        |    try {
+        |      /* do something tricky with the topping */
+        |    } catch (e: IllegalToppingException) {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun ifElse() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("isDelicious")
+          .addParameter("count", INT)
+          .returns(BOOLEAN)
+          .beginControlFlow("if (count > 0)")
+          .addStatement("return true")
+          .nextControlFlow("else")
+          .addStatement("return false")
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Boolean
+        |import kotlin.Int
+        |
+        |public class Taco {
+        |  public fun isDelicious(count: Int): Boolean {
+        |    if (count > 0) {
+        |      return true
+        |    } else {
+        |      return false
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun whenReturn() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("toppingPrice")
+          .addParameter("topping", String::class)
+          .beginControlFlow("return when(topping)")
+          .addStatement("%S -> 1", "beef")
+          .addStatement("%S -> 2", "lettuce")
+          .addStatement("%S -> 3", "cheese")
+          .addStatement("else -> throw IllegalToppingException(topping)")
+          .endControlFlow()
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public fun toppingPrice(topping: String) = when(topping) {
+        |    "beef" -> 1
+        |    "lettuce" -> 2
+        |    "cheese" -> 3
+        |    else -> throw IllegalToppingException(topping)
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun literalFromAnything() {
+    val value = object : Any() {
+      override fun toString(): String {
+        return "foo"
+      }
+    }
+    assertThat(CodeBlock.of("%L", value).toString()).isEqualTo("foo")
+  }
+
+  @Test fun nameFromCharSequence() {
+    assertThat(CodeBlock.of("%N", "text").toString()).isEqualTo("text")
+  }
+
+  @Test fun nameFromProperty() {
+    val property = PropertySpec.builder("property", String::class).build()
+    assertThat(CodeBlock.of("%N", property).toString()).isEqualTo("`property`")
+  }
+
+  @Test fun nameFromParameter() {
+    val parameter = ParameterSpec.builder("parameter", String::class).build()
+    assertThat(CodeBlock.of("%N", parameter).toString()).isEqualTo("parameter")
+  }
+
+  @Test fun nameFromFunction() {
+    val funSpec = FunSpec.builder("method")
+      .addModifiers(KModifier.ABSTRACT)
+      .returns(String::class)
+      .build()
+    assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("method")
+  }
+
+  @Test fun nameFromType() {
+    val type = TypeSpec.classBuilder("Type").build()
+    assertThat(CodeBlock.of("%N", type).toString()).isEqualTo("Type")
+  }
+
+  @Test fun nameFromUnsupportedType() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%N", String::class)
+    }.hasMessageThat().isEqualTo("expected name but was " + String::class)
+  }
+
+  @Test fun stringFromAnything() {
+    val value = object : Any() {
+      override fun toString(): String {
+        return "foo"
+      }
+    }
+    assertThat(CodeBlock.of("%S", value).toString()).isEqualTo("\"foo\"")
+  }
+
+  @Test fun stringFromNull() {
+    assertThat(CodeBlock.of("%S", null).toString()).isEqualTo("null")
+  }
+
+  @Test fun typeFromTypeName() {
+    val typeName = String::class.asTypeName()
+    assertThat(CodeBlock.of("%T", typeName).toString()).isEqualTo("kotlin.String")
+  }
+
+  @Test fun typeFromTypeMirror() {
+    val mirror = getElement(String::class).asType()
+    assertThat(CodeBlock.of("%T", mirror).toString()).isEqualTo("java.lang.String")
+  }
+
+  @Test fun typeFromTypeElement() {
+    val element = getElement(String::class)
+    assertThat(CodeBlock.of("%T", element).toString()).isEqualTo("java.lang.String")
+  }
+
+  @Test fun typeFromReflectType() {
+    assertThat(CodeBlock.of("%T", String::class).toString()).isEqualTo("kotlin.String")
+  }
+
+  @Test fun typeFromUnsupportedType() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%T", "kotlin.String")
+    }.hasMessageThat().isEqualTo("expected type but was kotlin.String")
+  }
+
+  @Test fun tooFewArguments() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%S")
+    }.hasMessageThat().isEqualTo("index 1 for '%S' not in range (received 0 arguments)")
+  }
+
+  @Test fun unusedArgumentsRelative() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%L %L", "a", "b", "c")
+    }.hasMessageThat().isEqualTo("unused arguments: expected 2, received 3")
+  }
+
+  @Test fun unusedArgumentsIndexed() {
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1L %2L", "a", "b", "c")
+    }.hasMessageThat().isEqualTo("unused argument: %3")
+
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%1L %1L %1L", "a", "b", "c")
+    }.hasMessageThat().isEqualTo("unused arguments: %2, %3")
+
+    assertThrows<IllegalArgumentException> {
+      CodeBlock.builder().add("%3L %1L %3L %1L %3L", "a", "b", "c", "d")
+    }.hasMessageThat().isEqualTo("unused arguments: %2, %4")
+  }
+
+  @Test fun superClassOnlyValidForClasses() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.annotationBuilder("A").superclass(Any::class.asClassName())
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.enumBuilder("E").superclass(Any::class.asClassName())
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.interfaceBuilder("I").superclass(Any::class.asClassName())
+    }
+  }
+
+  @Test fun superClassConstructorParametersOnlyValidForClasses() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.annotationBuilder("A").addSuperclassConstructorParameter("")
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.enumBuilder("E").addSuperclassConstructorParameter("")
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.interfaceBuilder("I").addSuperclassConstructorParameter("")
+    }
+  }
+
+  @Test fun anonymousClassesCannotHaveModifiersOrTypeVariable() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.anonymousClassBuilder().addModifiers(PUBLIC)
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.anonymousClassBuilder().addTypeVariable(TypeVariableName("T")).build()
+    }
+
+    assertThrows<IllegalStateException> {
+      TypeSpec.anonymousClassBuilder().addTypeVariables(listOf(TypeVariableName("T"))).build()
+    }
+  }
+
+  @Test fun invalidSuperClass() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.classBuilder("foo")
+        .superclass(List::class)
+        .superclass(Map::class)
+    }
+  }
+
+  @Test fun staticCodeBlock() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty("foo", String::class, KModifier.PRIVATE)
+      .addProperty(
+        PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+          .initializer("%S", "FOO")
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("toString")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(String::class)
+          .addStatement("return FOO")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  private val foo: String
+        |
+        |  private const val FOO: String = "FOO"
+        |
+        |  public override fun toString(): String = FOO
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun initializerBlockInRightPlace() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty("foo", String::class, KModifier.PRIVATE)
+      .addProperty(
+        PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+          .initializer("%S", "FOO")
+          .build(),
+      )
+      .addFunction(FunSpec.constructorBuilder().build())
+      .addFunction(
+        FunSpec.builder("toString")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(String::class)
+          .addStatement("return FOO")
+          .build(),
+      )
+      .addInitializerBlock(
+        CodeBlock.builder()
+          .addStatement("foo = %S", "FOO")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  private val foo: String
+        |
+        |  private const val FOO: String = "FOO"
+        |
+        |  init {
+        |    foo = "FOO"
+        |  }
+        |
+        |  public constructor()
+        |
+        |  public override fun toString(): String = FOO
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun initializersToBuilder() {
+    // Tests if toBuilder() contains instance initializers
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(PropertySpec.builder("foo", String::class, KModifier.PRIVATE).build())
+      .addProperty(
+        PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+          .initializer("%S", "FOO")
+          .build(),
+      )
+      .addFunction(FunSpec.constructorBuilder().build())
+      .addFunction(
+        FunSpec.builder("toString")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .returns(String::class)
+          .addStatement("return FOO")
+          .build(),
+      )
+      .build()
+
+    val recreatedTaco = taco.toBuilder().build()
+    assertThat(toString(taco)).isEqualTo(toString(recreatedTaco))
+
+    val initializersAdded = taco.toBuilder()
+      .addInitializerBlock(
+        CodeBlock.builder()
+          .addStatement("foo = %S", "instanceFoo")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(initializersAdded)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  private val foo: String
+        |
+        |  private const val FOO: String = "FOO"
+        |
+        |  init {
+        |    foo = "instanceFoo"
+        |  }
+        |
+        |  public constructor()
+        |
+        |  public override fun toString(): String = FOO
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun generalToBuilderEqualityTest() {
+    val originatingElement = FakeElement()
+    val comprehensiveTaco = TypeSpec.classBuilder("Taco")
+      .addKdoc("SuperTaco")
+      .addAnnotation(SuppressWarnings::class)
+      .addModifiers(DATA)
+      .addTypeVariable(TypeVariableName.of("State", listOf(ANY), IN).copy(reified = true))
+      .addType(
+        TypeSpec.companionObjectBuilder()
+          .build(),
+      )
+      .addType(
+        TypeSpec.classBuilder("InnerTaco")
+          .addModifiers(INNER)
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .build(),
+      )
+      .superclass(ClassName("texmexfood", "TortillaBased"))
+      .addSuperclassConstructorParameter("true")
+      .addProperty(
+        PropertySpec.builder("meat", ClassName("texmexfood", "Meat"))
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("fold")
+          .build(),
+      )
+      .addSuperinterface(ClassName("texmexfood", "Consumable"))
+      .addOriginatingElement(originatingElement)
+      .build()
+
+    val newTaco = comprehensiveTaco.toBuilder().build()
+    assertThat(newTaco).isEqualTo(comprehensiveTaco)
+    assertThat(newTaco.originatingElements).containsExactly(originatingElement)
+  }
+
+  @Test fun generalEnumToBuilderEqualityTest() {
+    val bestTexMexEnum = TypeSpec.enumBuilder("BestTexMex")
+      .addEnumConstant("TACO")
+      .addEnumConstant("BREAKFAST_TACO")
+      .build()
+
+    assertThat(bestTexMexEnum.toBuilder().build()).isEqualTo(bestTexMexEnum)
+  }
+
+  @Test fun generalInterfaceBuilderEqualityTest() {
+    val taco = TypeSpec.interfaceBuilder("Taco")
+      .addProperty("isVegan", Boolean::class)
+      .addSuperinterface(Runnable::class)
+      .build()
+    assertThat(taco.toBuilder().build()).isEqualTo(taco)
+  }
+
+  @Test fun generalAnnotationBuilderEqualityTest() {
+    val annotation = TypeSpec.annotationBuilder("MyAnnotation")
+      .addModifiers(KModifier.PUBLIC)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("test", Int::class)
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("test", Int::class)
+          .initializer("test")
+          .build(),
+      )
+      .build()
+    assertThat(annotation.toBuilder().build()).isEqualTo(annotation)
+  }
+
+  @Test fun generalExpectClassBuilderEqualityTest() {
+    val expectSpec = TypeSpec.expectClassBuilder("AtmoicRef")
+      .addModifiers(KModifier.INTERNAL)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("value", Int::class)
+          .build(),
+      )
+      .addProperty(PropertySpec.builder("value", Int::class).build())
+      .addFunction(
+        FunSpec.builder("get")
+          .returns(Int::class)
+          .build(),
+      )
+      .build()
+    assertThat(expectSpec.toBuilder().build()).isEqualTo(expectSpec)
+  }
+
+  @Test fun generalObjectBuilderEqualityTest() {
+    val objectSpec = TypeSpec.objectBuilder("MyObject")
+      .addModifiers(KModifier.PUBLIC)
+      .addProperty("tacos", Int::class)
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+    assertThat(objectSpec.toBuilder().build()).isEqualTo(objectSpec)
+  }
+
+  @Test fun generalAnonymousClassBuilderEqualityTest() {
+    val anonObjectSpec = TypeSpec.anonymousClassBuilder()
+      .addSuperinterface(Runnable::class)
+      .addFunction(
+        FunSpec.builder("run")
+          .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+          .build(),
+      )
+      .build()
+    assertThat(anonObjectSpec.toBuilder().build()).isEqualTo(anonObjectSpec)
+  }
+
+  @Test fun initializerBlockUnsupportedExceptionOnInterface() {
+    val interfaceBuilder = TypeSpec.interfaceBuilder("Taco")
+    assertThrows<IllegalStateException> {
+      interfaceBuilder.addInitializerBlock(CodeBlock.builder().build())
+    }
+  }
+
+  @Test fun initializerBlockUnsupportedExceptionOnAnnotation() {
+    val annotationBuilder = TypeSpec.annotationBuilder("Taco")
+    assertThrows<IllegalStateException> {
+      annotationBuilder.addInitializerBlock(CodeBlock.builder().build())
+    }
+  }
+
+  @Test fun equalsAndHashCode() {
+    var a = TypeSpec.interfaceBuilder("taco").build()
+    var b = TypeSpec.interfaceBuilder("taco").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = TypeSpec.classBuilder("taco").build()
+    b = TypeSpec.classBuilder("taco").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build()
+    b = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+    a = TypeSpec.annotationBuilder("taco").build()
+    b = TypeSpec.annotationBuilder("taco").build()
+    assertThat(a == b).isTrue()
+    assertThat(a.hashCode()).isEqualTo(b.hashCode())
+  }
+
+  @Test fun classNameFactories() {
+    val className = ClassName("com.example", "Example")
+    assertThat(TypeSpec.classBuilder(className).build().name).isEqualTo("Example")
+    assertThat(TypeSpec.interfaceBuilder(className).build().name).isEqualTo("Example")
+    assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example")
+    assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example")
+  }
+
+  @Test fun objectType() {
+    val type = TypeSpec.objectBuilder("MyObject")
+      .addModifiers(KModifier.PUBLIC)
+      .addProperty("tacos", Int::class)
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.Unit
+        |
+        |public object MyObject {
+        |  public val tacos: Int
+        |
+        |  init {
+        |  }
+        |
+        |  public fun test(): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun objectClassWithSupertype() {
+    val superclass = ClassName("com.squareup.wire", "Message")
+    val type = TypeSpec.objectBuilder("MyObject")
+      .addModifiers(KModifier.PUBLIC)
+      .superclass(superclass)
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.wire.Message
+        |import kotlin.Unit
+        |
+        |public object MyObject : Message() {
+        |  init {
+        |  }
+        |
+        |  public fun test(): Unit {
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObject() {
+    val companion = TypeSpec.companionObjectBuilder()
+      .addProperty(
+        PropertySpec.builder("tacos", Int::class)
+          .initializer("%L", 42)
+          .build(),
+      )
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    val type = TypeSpec.classBuilder("MyClass")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.Unit
+        |
+        |public class MyClass {
+        |  public companion object {
+        |    public val tacos: Int = 42
+        |
+        |    public fun test(): Unit {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObjectWithInitializer() {
+    val companion = TypeSpec.companionObjectBuilder()
+      .addProperty(
+        PropertySpec.builder("tacos", Int::class)
+          .mutable()
+          .initializer("%L", 24)
+          .build(),
+      )
+      .addInitializerBlock(
+        CodeBlock.builder()
+          .addStatement("tacos = %L", 42)
+          .build(),
+      )
+      .build()
+
+    val type = TypeSpec.classBuilder("MyClass")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public class MyClass {
+        |  public companion object {
+        |    public var tacos: Int = 24
+        |
+        |    init {
+        |      tacos = 42
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObjectWithName() {
+    val companion = TypeSpec.companionObjectBuilder("Factory")
+      .addFunction(FunSpec.builder("tacos").build())
+      .build()
+
+    val type = TypeSpec.classBuilder("MyClass")
+      .addType(companion)
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public class MyClass {
+        |  public companion object Factory {
+        |    public fun tacos(): Unit {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObjectOnInterface() {
+    val companion = TypeSpec.companionObjectBuilder()
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    val type = TypeSpec.interfaceBuilder("MyInterface")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public interface MyInterface {
+        |  public companion object {
+        |    public fun test(): Unit {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObjectOnEnum() {
+    val companion = TypeSpec.companionObjectBuilder()
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    val enumBuilder = TypeSpec.enumBuilder("MyEnum")
+      .addEnumConstant("FOO")
+      .addEnumConstant("BAR")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+      .build()
+
+    assertThat(toString(enumBuilder)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Unit
+        |
+        |public enum class MyEnum {
+        |  FOO,
+        |  BAR,
+        |  ;
+        |
+        |  public companion object {
+        |    public fun test(): Unit {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun companionObjectOnObjectNotAllowed() {
+    val companion = TypeSpec.companionObjectBuilder()
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    val objectBuilder = TypeSpec.objectBuilder("MyObject")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+
+    assertThrows<IllegalArgumentException> {
+      objectBuilder.build()
+    }
+  }
+
+  @Test fun companionObjectSuper() {
+    val superclass = ClassName("com.squareup.wire", "Message")
+    val companion = TypeSpec.companionObjectBuilder()
+      .superclass(superclass)
+      .addFunction(
+        FunSpec.builder("test")
+          .addModifiers(KModifier.PUBLIC)
+          .build(),
+      )
+      .build()
+
+    val type = TypeSpec.classBuilder("MyClass")
+      .addModifiers(KModifier.PUBLIC)
+      .addType(companion)
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import com.squareup.wire.Message
+        |import kotlin.Unit
+        |
+        |public class MyClass {
+        |  public companion object : Message() {
+        |    public fun test(): Unit {
+        |    }
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun propertyInPrimaryConstructor() {
+    val type = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("a", Int::class)
+          .addParameter("b", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("a", Int::class)
+          .initializer("a")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("b", String::class)
+          .initializer("b")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public class Taco(
+        |  public val a: Int,
+        |  public val b: String,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun propertyWithKdocInPrimaryConstructor() {
+    val type = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("a", Int::class)
+          .addParameter("b", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("a", Int::class)
+          .initializer("a")
+          .addKdoc("KDoc\n")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("b", String::class)
+          .initializer("b")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public class Taco(
+        |  /**
+        |   * KDoc
+        |   */
+        |  public val a: Int,
+        |  public val b: String,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedConstructor() {
+    val injectAnnotation = ClassName("javax.inject", "Inject")
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+    |package com.squareup.tacos
+    |
+    |import javax.inject.Inject
+    |
+    |public class Taco @Inject constructor()
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun internalConstructor() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addModifiers(INTERNAL)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+    |package com.squareup.tacos
+    |
+    |public class Taco internal constructor()
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun annotatedInternalConstructor() {
+    val injectAnnotation = ClassName("javax.inject", "Inject")
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+          .addModifiers(INTERNAL)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+    |package com.squareup.tacos
+    |
+    |import javax.inject.Inject
+    |
+    |public class Taco @Inject internal constructor()
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleAnnotationsInternalConstructor() {
+    val injectAnnotation = ClassName("javax.inject", "Inject")
+    val namedAnnotation = ClassName("javax.inject", "Named")
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+          .addAnnotation(AnnotationSpec.builder(namedAnnotation).build())
+          .addModifiers(INTERNAL)
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+    |package com.squareup.tacos
+    |
+    |import javax.inject.Inject
+    |import javax.inject.Named
+    |
+    |public class Taco @Inject @Named internal constructor()
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun importNonNullableProperty() {
+    val type = String::class.asTypeName()
+    val taco = TypeSpec.classBuilder("Taco")
+      .addProperty(
+        PropertySpec.builder("taco", type.copy(nullable = false))
+          .initializer("%S", "taco")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("nullTaco", type.copy(nullable = true))
+          .initializer("null")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |
+        |public class Taco {
+        |  public val taco: String = "taco"
+        |
+        |  public val nullTaco: String? = null
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun superclassConstructorParams() {
+    val taco = TypeSpec.classBuilder("Foo")
+      .superclass(ClassName(tacosPackage, "Bar"))
+      .addSuperclassConstructorParameter("%S", "foo")
+      .addSuperclassConstructorParameter(CodeBlock.of("%L", 42))
+      .build()
+
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |public class Foo : Bar("foo", 42)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun superclassConstructorParamsForbiddenForAnnotation() {
+    assertThrows<IllegalStateException> {
+      TypeSpec.annotationBuilder("Taco")
+        .addSuperclassConstructorParameter("%S", "foo")
+    }
+  }
+
+  @Test fun classExtendsNoPrimaryConstructor() {
+    val typeSpec = TypeSpec.classBuilder("IoException")
+      .superclass(Exception::class)
+      .addFunction(FunSpec.constructorBuilder().build())
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.lang.Exception
+      |
+      |public class IoException : Exception {
+      |  public constructor()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classExtendsNoPrimaryOrSecondaryConstructor() {
+    val typeSpec = TypeSpec.classBuilder("IoException")
+      .superclass(Exception::class)
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.lang.Exception
+      |
+      |public class IoException : Exception()
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classExtendsNoPrimaryConstructorButSuperclassParams() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("IoException")
+        .superclass(Exception::class)
+        .addSuperclassConstructorParameter("%S", "hey")
+        .addFunction(FunSpec.constructorBuilder().build())
+        .build()
+    }.hasMessageThat().isEqualTo(
+      "types without a primary constructor cannot specify secondary constructors and superclass constructor parameters",
+    )
+  }
+
+  @Test fun constructorWithDefaultParamValue() {
+    val type = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("a", Int::class)
+              .defaultValue("1")
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec
+              .builder("b", String::class.asTypeName().copy(nullable = true))
+              .defaultValue("null")
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("a", Int::class)
+          .initializer("a")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("b", String::class.asTypeName().copy(nullable = true))
+          .initializer("b")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public class Taco(
+        |  public val a: Int = 1,
+        |  public val b: String? = null,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun constructorDelegation() {
+    val type = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("a", String::class.asTypeName().copy(nullable = true))
+          .addParameter("b", String::class.asTypeName().copy(nullable = true))
+          .addParameter("c", String::class.asTypeName().copy(nullable = true))
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("a", String::class.asTypeName().copy(nullable = true))
+          .initializer("a")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("b", String::class.asTypeName().copy(nullable = true))
+          .initializer("b")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("c", String::class.asTypeName().copy(nullable = true))
+          .initializer("c")
+          .build(),
+      )
+      .addFunction(
+        FunSpec.constructorBuilder()
+          .addParameter("map", Map::class.parameterizedBy(String::class, String::class))
+          .callThisConstructor(
+            CodeBlock.of("map[%S]", "a"),
+            CodeBlock.of("map[%S]", "b"),
+            CodeBlock.of("map[%S]", "c"),
+          )
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.collections.Map
+        |
+        |public class Taco(
+        |  public val a: String?,
+        |  public val b: String?,
+        |  public val c: String?,
+        |) {
+        |  public constructor(map: Map<String, String>) : this(map["a"], map["b"], map["c"])
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun internalFunForbiddenInInterface() {
+    val type = TypeSpec.interfaceBuilder("ITaco")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunction(
+        FunSpec.builder("eat")
+          .addModifiers(ABSTRACT, INTERNAL)
+          .build(),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, INTERNAL] must contain none of [INTERNAL, PROTECTED]")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunctions(
+        listOf(
+          FunSpec.builder("eat")
+            .addModifiers(ABSTRACT, INTERNAL)
+            .build(),
+        ),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, INTERNAL] must contain none of [INTERNAL, PROTECTED]")
+  }
+
+  @Test fun privateAbstractFunForbiddenInInterface() {
+    val type = TypeSpec.interfaceBuilder("ITaco")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunction(
+        FunSpec.builder("eat")
+          .addModifiers(ABSTRACT, PRIVATE)
+          .build(),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, PRIVATE] must contain none or only one of [ABSTRACT, PRIVATE]")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunctions(
+        listOf(
+          FunSpec.builder("eat")
+            .addModifiers(ABSTRACT, PRIVATE)
+            .build(),
+        ),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, PRIVATE] must contain none or only one of [ABSTRACT, PRIVATE]")
+  }
+
+  @Test fun internalFunForbiddenInAnnotation() {
+    val type = TypeSpec.annotationBuilder("Taco")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunction(
+        FunSpec.builder("eat")
+          .addModifiers(INTERNAL)
+          .build(),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("annotation class Taco.eat requires modifiers [PUBLIC, ABSTRACT]")
+
+    assertThrows<IllegalArgumentException> {
+      type.addFunctions(
+        listOf(
+          FunSpec.builder("eat")
+            .addModifiers(INTERNAL)
+            .build(),
+        ),
+      )
+        .build()
+    }.hasMessageThat().isEqualTo("annotation class Taco.eat requires modifiers [PUBLIC, ABSTRACT]")
+  }
+
+  @Test fun classHeaderFormatting() {
+    val typeSpec = TypeSpec.classBuilder("Person")
+      .addModifiers(DATA)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("id", Int::class)
+          .addParameter("name", String::class)
+          .addParameter("surname", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("id", Int::class, KModifier.OVERRIDE)
+          .initializer("id")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("name", String::class, KModifier.OVERRIDE)
+          .initializer("name")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("surname", String::class, KModifier.OVERRIDE)
+          .initializer("surname")
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.String
+      |
+      |public data class Person(
+      |  public override val id: Int,
+      |  public override val name: String,
+      |  public override val surname: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun classHeaderAnnotations() {
+    val idParameterSpec = ParameterSpec.builder("id", Int::class)
+      .addAnnotation(ClassName("com.squareup.kotlinpoet", "Id"))
+      .defaultValue("1")
+      .build()
+
+    val typeSpec = TypeSpec.classBuilder("Person")
+      .addModifiers(DATA)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(idParameterSpec)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("id", Int::class)
+          .addModifiers(PRIVATE)
+          .initializer("id")
+          .addAnnotation(ClassName("com.squareup.kotlinpoet", "OrderBy"))
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import com.squareup.kotlinpoet.Id
+      |import com.squareup.kotlinpoet.OrderBy
+      |import kotlin.Int
+      |
+      |public data class Person(
+      |  @OrderBy
+      |  @Id
+      |  private val id: Int = 1,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun literalPropertySpec() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("shell")
+          .addCode(
+            CodeBlock.of(
+              "%L",
+              PropertySpec.builder("taco1", String::class.asTypeName())
+                .initializer("%S", "Taco!").build(),
+            ),
+          )
+          .addCode(
+            CodeBlock.of(
+              "%L",
+              PropertySpec.builder("taco2", String::class.asTypeName().copy(nullable = true))
+                .initializer("null")
+                .build(),
+            ),
+          )
+          .addCode(
+            CodeBlock.of(
+              "%L",
+              PropertySpec.builder("taco3", String::class.asTypeName(), KModifier.LATEINIT)
+                .mutable()
+                .build(),
+            ),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.String
+        |import kotlin.Unit
+        |
+        |public class Taco {
+        |  public fun shell(): Unit {
+        |    val taco1: String = "Taco!"
+        |    val taco2: String? = null
+        |    lateinit var taco3: String
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun basicDelegateTest() {
+    val type = TypeSpec.classBuilder("Guac")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("somethingElse", String::class)
+          .build(),
+      )
+      .addSuperinterface(
+        Consumer::class.parameterizedBy(String::class),
+        CodeBlock.of("({ println(it) })"),
+      )
+      .build()
+
+    val expect = """
+        |package com.squareup.tacos
+        |
+        |import java.util.function.Consumer
+        |import kotlin.String
+        |
+        |public class Guac(
+        |  somethingElse: String,
+        |) : Consumer<String> by ({ println(it) })
+        |
+    """.trimMargin()
+
+    assertThat(toString(type)).isEqualTo(expect)
+  }
+
+  @Test fun testDelegateOnObject() {
+    val type = TypeSpec.objectBuilder("Guac")
+      .addSuperinterface(
+        Consumer::class.parameterizedBy(String::class),
+        CodeBlock.of("({ println(it) })"),
+      )
+      .build()
+
+    val expect = """
+        |package com.squareup.tacos
+        |
+        |import java.util.function.Consumer
+        |import kotlin.String
+        |
+        |public object Guac : Consumer<String> by ({ println(it) })
+        |
+    """.trimMargin()
+
+    assertThat(toString(type)).isEqualTo(expect)
+  }
+
+  @Test fun testMultipleDelegates() {
+    val type = TypeSpec.classBuilder("StringToInteger")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .build(),
+      )
+      .addSuperinterface(
+        Function::class.parameterizedBy(String::class, Int::class),
+        CodeBlock.of("Function ({ text -> text.toIntOrNull() ?: 0 })"),
+      )
+      .addSuperinterface(
+        Runnable::class,
+        CodeBlock.of("Runnable ({ %T.debug(\"Hello world\") })", Logger::class.asTypeName()),
+      )
+      .build()
+
+    val expect = """
+        |package com.squareup.tacos
+        |
+        |import java.lang.Runnable
+        |import java.util.logging.Logger
+        |import kotlin.Function
+        |import kotlin.Int
+        |import kotlin.String
+        |
+        |public class StringToInteger() : Function<String, Int> by Function ({ text -> text.toIntOrNull() ?:
+        |    0 }), Runnable by Runnable ({ Logger.debug("Hello world") })
+        |
+    """.trimMargin()
+
+    assertThat(toString(type)).isEqualTo(expect)
+  }
+
+  @Test fun testNoSuchParameterDelegate() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Taco")
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("other", String::class)
+            .build(),
+        )
+        .addSuperinterface(KFunction::class, "notOther")
+        .build()
+    }.hasMessageThat().isEqualTo("no such constructor parameter 'notOther' to delegate to for type 'Taco'")
+  }
+
+  @Test fun failAddParamDelegateWhenNullConstructor() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Taco")
+        .addSuperinterface(Runnable::class, "etc")
+        .build()
+    }.hasMessageThat().isEqualTo("delegating to constructor parameter requires not-null constructor")
+  }
+
+  @Test fun testAddedDelegateByParamName() {
+    val type = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("superString", Function::class)
+          .build(),
+      )
+      .addSuperinterface(Function::class, "superString")
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+          |package com.squareup.tacos
+          |
+          |import kotlin.Function
+          |
+          |public class Taco(
+          |  superString: Function,
+          |) : Function by superString
+          |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun failOnAddExistingDelegateType() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Taco")
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("superString", Function::class)
+            .build(),
+        )
+        .addSuperinterface(Function::class, CodeBlock.of("{ print(Hello) }"))
+        .addSuperinterface(Function::class, "superString")
+        .build()
+      fail()
+    }.hasMessageThat().isEqualTo(
+      "'Taco' can not delegate to kotlin.Function " +
+        "by superString with existing declaration by { print(Hello) }",
+    )
+  }
+
+  @Test fun testDelegateIfaceWithOtherParamTypeName() {
+    val entity = ClassName(tacosPackage, "Entity")
+    val entityBuilder = ClassName(tacosPackage, "EntityBuilder")
+    val type = TypeSpec.classBuilder("EntityBuilder")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder(
+              "argBuilder",
+              ClassName(tacosPackage, "Payload")
+                .parameterizedBy(entityBuilder, entity),
+            )
+              .defaultValue("Payload.create()")
+              .build(),
+          )
+          .build(),
+      )
+      .addSuperinterface(
+        ClassName(tacosPackage, "TypeBuilder")
+          .parameterizedBy(entityBuilder, entity),
+        "argBuilder",
+      )
+      .build()
+
+    assertThat(toString(type)).isEqualTo(
+      """
+          |package com.squareup.tacos
+          |
+          |public class EntityBuilder(
+          |  argBuilder: Payload<EntityBuilder, Entity> = Payload.create(),
+          |) : TypeBuilder<EntityBuilder, Entity> by argBuilder
+          |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalClassFunctionHasNoBody() {
+    val typeSpec = TypeSpec.classBuilder("Foo")
+      .addModifiers(KModifier.EXTERNAL)
+      .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public external class Foo {
+      |  public fun bar(): Unit
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalInterfaceWithMembers() {
+    val typeSpec = TypeSpec.interfaceBuilder("Foo")
+      .addModifiers(KModifier.EXTERNAL)
+      .addProperty(PropertySpec.builder("baz", String::class).addModifiers(KModifier.EXTERNAL).build())
+      .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |public external interface Foo {
+      |  public val baz: String
+      |
+      |  public fun bar(): Unit
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalObjectWithMembers() {
+    val typeSpec = TypeSpec.objectBuilder("Foo")
+      .addModifiers(KModifier.EXTERNAL)
+      .addProperty(PropertySpec.builder("baz", String::class).addModifiers(KModifier.EXTERNAL).build())
+      .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.Unit
+      |
+      |public external object Foo {
+      |  public val baz: String
+      |
+      |  public fun bar(): Unit
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun externalClassWithNestedTypes() {
+    val typeSpec = TypeSpec.classBuilder("Foo")
+      .addModifiers(KModifier.EXTERNAL)
+      .addType(
+        TypeSpec.classBuilder("Nested1")
+          .addModifiers(KModifier.EXTERNAL)
+          .addType(
+            TypeSpec.objectBuilder("Nested2")
+              .addModifiers(KModifier.EXTERNAL)
+              .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+              .build(),
+          )
+          .addFunction(FunSpec.builder("baz").addModifiers(KModifier.EXTERNAL).build())
+          .build(),
+      )
+      .addType(
+        TypeSpec.companionObjectBuilder()
+          .addModifiers(KModifier.EXTERNAL)
+          .addFunction(FunSpec.builder("qux").addModifiers(KModifier.EXTERNAL).build())
+          .build(),
+      )
+      .build()
+
+    assertThat(toString(typeSpec)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |
+      |public external class Foo {
+      |  public class Nested1 {
+      |    public fun baz(): Unit
+      |
+      |    public object Nested2 {
+      |      public fun bar(): Unit
+      |    }
+      |  }
+      |
+      |  public companion object {
+      |    public fun qux(): Unit
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun isEnum() {
+    val enum = TypeSpec.enumBuilder("Topping")
+      .addEnumConstant("CHEESE")
+      .build()
+    assertThat(enum.isEnum).isTrue()
+  }
+
+  @Test fun isAnnotation() {
+    val annotation = TypeSpec.annotationBuilder("Taco")
+      .build()
+    assertThat(annotation.isAnnotation).isTrue()
+  }
+
+  @Test fun escapePunctuationInTypeName() {
+    assertThat(TypeSpec.classBuilder("With-Hyphen").build().toString()).isEqualTo(
+      """
+      |public class `With-Hyphen`
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun multipleCompanionObjects() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Taco")
+        .addTypes(
+          listOf(
+            TypeSpec.companionObjectBuilder()
+              .build(),
+            TypeSpec.companionObjectBuilder()
+              .build(),
+          ),
+        )
+        .build()
+    }
+  }
+
+  @Test fun objectKindIsCompanion() {
+    val companionObject = TypeSpec.companionObjectBuilder()
+      .build()
+    assertThat(companionObject.isCompanion).isTrue()
+  }
+
+  @Test fun typeNamesCollision() {
+    val sqlTaco = ClassName("java.sql", "Taco")
+    val source = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addModifiers(DATA)
+          .addProperty(
+            PropertySpec.builder("madeFreshDatabaseDate", sqlTaco)
+              .initializer("madeFreshDatabaseDate")
+              .build(),
+          )
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("madeFreshDatabaseDate", sqlTaco)
+              .addParameter("fooNt", INT)
+              .build(),
+          )
+          .addFunction(
+            FunSpec.constructorBuilder()
+              .addParameter("anotherTaco", ClassName("com.squareup.tacos", "Taco"))
+              .callThisConstructor(CodeBlock.of("%T.defaultInstance(), 0", sqlTaco))
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(source.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |
+      |public data class Taco(
+      |  public val madeFreshDatabaseDate: java.sql.Taco,
+      |  fooNt: Int,
+      |) {
+      |  public constructor(anotherTaco: Taco) : this(java.sql.Taco.defaultInstance(), 0)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun modifyAnnotations() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addAnnotation(
+        AnnotationSpec.builder(JvmName::class.asClassName())
+          .addMember("name = %S", "jvmWord")
+          .build(),
+      )
+
+    val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+      .addMember("name = %S", "javaWord")
+      .build()
+    builder.annotationSpecs.clear()
+    builder.annotationSpecs.add(javaWord)
+
+    assertThat(builder.build().annotationSpecs).containsExactly(javaWord)
+  }
+
+  @Test fun modifyTypeVariableNames() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("V"))
+
+    val tVar = TypeVariableName("T")
+    builder.typeVariables.clear()
+    builder.typeVariables.add(tVar)
+
+    assertThat(builder.build().typeVariables).containsExactly(tVar)
+  }
+
+  @Test fun modifyFunctions() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addFunction(FunSpec.builder("topping").build())
+
+    val seasoning = FunSpec.builder("seasoning").build()
+    builder.funSpecs.clear()
+    builder.funSpecs.add(seasoning)
+
+    assertThat(builder.build().funSpecs).containsExactly(seasoning)
+  }
+
+  @Test fun modifyTypeSpecs() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addType(TypeSpec.classBuilder("Topping").build())
+
+    val seasoning = TypeSpec.classBuilder("Seasoning").build()
+    builder.typeSpecs.clear()
+    builder.typeSpecs.add(seasoning)
+
+    assertThat(builder.build().typeSpecs).containsExactly(seasoning)
+  }
+
+  @Test fun modifySuperinterfaces() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addSuperinterface(List::class)
+
+    builder.superinterfaces.clear()
+    builder.superinterfaces[Set::class.asTypeName()] = CodeBlock.EMPTY
+
+    assertThat(builder.build().superinterfaces)
+      .containsExactlyEntriesIn(mapOf(Set::class.asTypeName() to CodeBlock.EMPTY))
+  }
+
+  @Test fun modifyProperties() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addProperty(PropertySpec.builder("topping", String::class.asClassName()).build())
+
+    val seasoning = PropertySpec.builder("seasoning", String::class.asClassName()).build()
+    builder.propertySpecs.clear()
+    builder.propertySpecs.add(seasoning)
+
+    assertThat(builder.build().propertySpecs).containsExactly(seasoning)
+  }
+
+  @Test fun modifyEnumConstants() {
+    val builder = TypeSpec.enumBuilder("Taco")
+      .addEnumConstant("TOPPING")
+
+    builder.enumConstants.clear()
+    builder.enumConstants["SEASONING"] = TypeSpec.anonymousClassBuilder().build()
+
+    assertThat(builder.build().enumConstants)
+      .containsExactlyEntriesIn(mapOf("SEASONING" to TypeSpec.anonymousClassBuilder().build()))
+  }
+
+  @Test fun modifySuperclassConstructorParams() {
+    val builder = TypeSpec.classBuilder("Taco")
+      .addSuperclassConstructorParameter(CodeBlock.of("seasoning = %S", "mild"))
+
+    val seasoning = CodeBlock.of("seasoning = %S", "spicy")
+    builder.superclassConstructorParameters.clear()
+    builder.superclassConstructorParameters.add(seasoning)
+
+    assertThat(builder.build().superclassConstructorParameters).containsExactly(seasoning)
+  }
+
+  // https://github.com/square/kotlinpoet/issues/565
+  @Test fun markerEnum() {
+    val spec = TypeSpec.enumBuilder("Topping")
+      .build()
+    assertThat(spec.toString()).isEqualTo(
+      """
+      |public enum class Topping
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/586
+  @Test fun classKdocWithoutTags() {
+    val typeSpec = TypeSpec.classBuilder("Foo")
+      .addKdoc("blah blah")
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+    |/**
+    | * blah blah
+    | */
+    |public class Foo
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun classWithPropertyKdoc() {
+    val typeSpec = TypeSpec.classBuilder("Foo")
+      .addProperty(
+        PropertySpec.builder("bar", String::class)
+          .addKdoc("The bar for your foo")
+          .build(),
+      )
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+    |public class Foo {
+    |  /**
+    |   * The bar for your foo
+    |   */
+    |  public val bar: kotlin.String
+    |}
+    |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/563
+  @Test fun kdocFormatting() {
+    val typeSpec = TypeSpec.classBuilder("MyType")
+      .addKdoc("This is a thing for stuff.")
+      .addProperty(
+        PropertySpec.builder("first", INT)
+          .initializer("first")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("second", INT)
+          .initializer("second")
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("third", INT)
+          .initializer("third")
+          .build(),
+      )
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addKdoc("Construct a thing!")
+          .addParameter(
+            ParameterSpec.builder("first", INT)
+              .addKdoc("the first thing")
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("second", INT)
+              .addKdoc("the second thing")
+              .build(),
+          )
+          .addParameter(
+            ParameterSpec.builder("third", INT)
+              .addKdoc("the third thing")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |/**
+      | * This is a thing for stuff.
+      | */
+      |public class MyType(
+      |  /**
+      |   * the first thing
+      |   */
+      |  public val first: kotlin.Int,
+      |  /**
+      |   * the second thing
+      |   */
+      |  public val second: kotlin.Int,
+      |  /**
+      |   * the third thing
+      |   */
+      |  public val third: kotlin.Int,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun primaryConstructorWithOneParameterKdocFormatting() {
+    val typeSpec = TypeSpec.classBuilder("MyType")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("first", INT)
+              .addKdoc("the first thing")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class MyType(
+      |  /**
+      |   * the first thing
+      |   */
+      |  first: kotlin.Int,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/594
+  @Test fun longComment() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .addFunction(
+        FunSpec.builder("getAnswer")
+          .returns(Int::class)
+          .addComment(
+            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+              "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+          )
+          .addStatement("return 42")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Int
+        |
+        |public class Taco {
+        |  public fun getAnswer(): Int {
+        |    // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+        |    return 42
+        |  }
+        |}
+        |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun originatingElementsIncludesThoseOfNestedTypes() {
+    val outerElement = FakeElement()
+    val innerElement = FakeElement()
+    val outer = TypeSpec.classBuilder("Outer")
+      .addOriginatingElement(outerElement)
+      .addType(
+        TypeSpec.classBuilder("Inner")
+          .addOriginatingElement(innerElement)
+          .build(),
+      )
+      .build()
+    assertThat(outer.originatingElements).containsExactly(outerElement, innerElement)
+  }
+
+  // https://github.com/square/kotlinpoet/issues/698
+  @Test fun escapeEnumConstants() {
+    val enum = TypeSpec.enumBuilder("MyEnum")
+      .addEnumConstant("test test")
+      .addEnumConstant("0constants")
+      .build()
+    assertThat(enum.toString()).isEqualTo(
+      """
+      |public enum class MyEnum {
+      |  `test test`,
+      |  `0constants`,
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun initOrdering_first() {
+    val type = TypeSpec.classBuilder("MyClass")
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addProperty("tacos", Int::class)
+      .build()
+
+    //language=kotlin
+    assertThat(toString(type)).isEqualTo(
+      """
+        package com.squareup.tacos
+
+        import kotlin.Int
+
+        public class MyClass {
+          init {
+          }
+
+          public val tacos: Int
+        }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun initOrdering_middle() {
+    val type = TypeSpec.classBuilder("MyClass")
+      .addProperty("tacos1", Int::class)
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addProperty("tacos2", Int::class)
+      .build()
+
+    //language=kotlin
+    assertThat(toString(type)).isEqualTo(
+      """
+        package com.squareup.tacos
+
+        import kotlin.Int
+
+        public class MyClass {
+          public val tacos1: Int
+
+          init {
+          }
+
+          public val tacos2: Int
+        }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun initOrdering_last() {
+    val type = TypeSpec.classBuilder("MyClass")
+      .addProperty("tacos", Int::class)
+      .addInitializerBlock(CodeBlock.builder().build())
+      .build()
+
+    //language=kotlin
+    assertThat(toString(type)).isEqualTo(
+      """
+        package com.squareup.tacos
+
+        import kotlin.Int
+
+        public class MyClass {
+          public val tacos: Int
+
+          init {
+          }
+        }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun initOrdering_constructorParamsExludedAfterIndex() {
+    val type = TypeSpec.classBuilder("MyClass")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("tacos1", Int::class)
+          .addParameter("tacos2", Int::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("tacos1", Int::class)
+          .initializer("tacos1")
+          .build(),
+      )
+      .addInitializerBlock(CodeBlock.builder().build())
+      .addProperty(
+        PropertySpec.builder("tacos2", Int::class)
+          .initializer("tacos2")
+          .build(),
+      )
+      .build()
+
+    //language=kotlin
+    assertThat(toString(type)).isEqualTo(
+      """
+        package com.squareup.tacos
+
+        import kotlin.Int
+
+        public class MyClass(
+          public val tacos1: Int,
+          tacos2: Int,
+        ) {
+          init {
+          }
+
+          public val tacos2: Int = tacos2
+        }
+
+      """.trimIndent(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/843
+  @Test fun kdocWithParametersWithoutClassKdoc() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(
+            ParameterSpec.builder("mild", Boolean::class)
+              .addKdoc(CodeBlock.of("%L", "Whether the taco is mild (ew) or crunchy (ye).\n"))
+              .build(),
+          )
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("mild", Boolean::class)
+          .addKdoc("No one likes mild tacos.")
+          .initializer("mild")
+          .build(),
+      )
+      .build()
+    assertThat(toString(taco)).isEqualTo(
+      """
+        |package com.squareup.tacos
+        |
+        |import kotlin.Boolean
+        |
+        |/**
+        | * @param mild Whether the taco is mild (ew) or crunchy (ye).
+        | */
+        |public class Taco(
+        |  /**
+        |   * No one likes mild tacos.
+        |   */
+        |  public val mild: Boolean,
+        |)
+        |
+      """.trimMargin(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/848
+  @Test fun escapeEnumConstantNames() {
+    val enum = TypeSpec
+      .enumBuilder("MyEnum")
+      .addEnumConstant("object")
+      .build()
+    assertThat(toString(enum)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |public enum class MyEnum {
+      |  `object`,
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  // https://youtrack.jetbrains.com/issue/KT-52315
+  @Test fun escapeHeaderAndImplAsEnumConstantNames() {
+    val primaryConstructor = FunSpec.constructorBuilder()
+      .addParameter("int", Int::class)
+      .build()
+    val enum = TypeSpec
+      .enumBuilder("MyEnum")
+      .primaryConstructor(primaryConstructor)
+      .addEnumConstant(
+        "header",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 1)
+          .build(),
+      )
+      .addEnumConstant(
+        "impl",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 2)
+          .build(),
+      )
+      .build()
+    assertThat(toString(enum)).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |
+      |public enum class MyEnum(
+      |  int: Int,
+      |) {
+      |  `header`(1),
+      |  `impl`(2),
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeClassNames() {
+    val type = TypeSpec.classBuilder("fun").build()
+    assertThat(type.toString()).isEqualTo(
+      """
+      |public class `fun`
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeInnerClassName() {
+    val tacoType = ClassName("com.squareup.tacos", "Taco", "object")
+    val funSpec = FunSpec.builder("printTaco")
+      .addParameter("taco", tacoType)
+      .addStatement("print(taco)")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun printTaco(taco: com.squareup.tacos.Taco.`object`): kotlin.Unit {
+      |  print(taco)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun escapeAllowedCharacters() {
+    val typeSpec = TypeSpec.classBuilder("A\$B")
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo("public class `A\$B`\n")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1011
+  @Test fun abstractInterfaceMembers() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.interfaceBuilder("Taco")
+          .addProperty("foo", String::class, ABSTRACT)
+          .addProperty(
+            PropertySpec.builder("fooWithDefault", String::class)
+              .initializer("%S", "defaultValue")
+              .build(),
+          )
+          .addFunction(
+            FunSpec.builder("bar")
+              .addModifiers(ABSTRACT)
+              .returns(String::class)
+              .build(),
+          )
+          .addFunction(
+            FunSpec.builder("barWithDefault")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    // language=kotlin
+    assertThat(file.toString()).isEqualTo(
+      """
+      package com.squareup.tacos
+
+      import kotlin.String
+      import kotlin.Unit
+
+      public interface Taco {
+        public val foo: String
+
+        public val fooWithDefault: String = "defaultValue"
+
+        public fun bar(): String
+
+        public fun barWithDefault(): Unit {
+        }
+      }
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun emptyConstructorGenerated() {
+    val taco = TypeSpec.classBuilder("Taco")
+      .primaryConstructor(FunSpec.constructorBuilder().build())
+      .build()
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(taco)
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      package com.squareup.tacos
+
+      public class Taco()
+
+      """.trimIndent(),
+    )
+  }
+
+  // Regression test for https://github.com/square/kotlinpoet/issues/1176
+  @Test fun `templates in class delegation blocks should be imported too`() {
+    val taco = TypeSpec.classBuilder("TacoShim")
+      .addSuperinterface(
+        ClassName("test", "Taco"),
+        CodeBlock.of("%T", ClassName("test", "RealTaco")),
+      )
+      .build()
+    val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+      .addType(taco)
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      package com.squareup.tacos
+
+      import test.RealTaco
+      import test.Taco
+
+      public class TacoShim : Taco by RealTaco
+
+      """.trimIndent(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1183
+  @Test fun `forbidden enum constant names`() {
+    var exception = assertFailsWith<IllegalArgumentException> {
+      TypeSpec.enumBuilder("Topping")
+        .addEnumConstant("name")
+    }
+    assertThat(exception.message).isEqualTo(
+      "constant with name \"name\" conflicts with a supertype member with the same name",
+    )
+
+    @Suppress("RemoveExplicitTypeArguments")
+    exception = assertFailsWith<IllegalArgumentException> {
+      TypeSpec.enumBuilder("Topping")
+        .addEnumConstant("ordinal")
+    }
+    assertThat(exception.message).isEqualTo(
+      "constant with name \"ordinal\" conflicts with a supertype member with the same name",
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1183
+  @Test fun `forbidden enum property names`() {
+    var exception = assertFailsWith<IllegalArgumentException> {
+      TypeSpec.enumBuilder("Topping")
+        .addProperty("name", String::class)
+    }
+    assertThat(exception.message).isEqualTo(
+      "name is a final supertype member and can't be redeclared or overridden",
+    )
+
+    @Suppress("RemoveExplicitTypeArguments")
+    exception = assertFailsWith<IllegalArgumentException> {
+      TypeSpec.enumBuilder("Topping")
+        .addProperty("ordinal", String::class)
+    }
+    assertThat(exception.message).isEqualTo(
+      "ordinal is a final supertype member and can't be redeclared or overridden",
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1234
+  @Test fun `enum constants are resolved`() {
+    val file = FileSpec.builder("com.example", "test")
+      .addType(
+        TypeSpec.enumBuilder("Foo")
+          .addProperty(
+            PropertySpec.builder("rawValue", String::class)
+              .initializer("%S", "")
+              .build(),
+          )
+          .addEnumConstant("String")
+          .build(),
+      )
+      .build()
+
+    assertThat(file.toString()).isEqualTo(
+      """
+    package com.example
+
+    public enum class Foo {
+      String,
+      ;
+
+      public val rawValue: kotlin.String = ""
+    }
+
+      """.trimIndent(),
+    )
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1035
+  @Test fun dataClassWithKeywordProperty() {
+    val parameter = ParameterSpec.builder("data", STRING).build()
+    val typeSpec = TypeSpec.classBuilder("Example")
+      .addModifiers(DATA)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter(parameter)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder(parameter.name, STRING)
+          .initializer("%N", parameter)
+          .build(),
+      )
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      public data class Example(
+        public val `data`: kotlin.String,
+      )
+
+      """.trimIndent(),
+    )
+  }
+
+  @Test fun contextReceiver() {
+    val typeSpec = TypeSpec.classBuilder("Example")
+      .contextReceivers(STRING)
+      .build()
+
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |context(kotlin.String)
+      |public class Example
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun contextReceiver_mustBeClass() {
+    val t = assertFailsWith<IllegalStateException> {
+      TypeSpec.interfaceBuilder("Example")
+        .contextReceivers(STRING)
+    }
+    assertThat(t).hasMessageThat().contains("contextReceivers can only be applied on simple classes")
+  }
+
+  companion object {
+    private const val donutsPackage = "com.squareup.donuts"
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt
new file mode 100644
index 0000000..bca1a7c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.TypeVariableName.Companion.NULLABLE_ANY_LIST
+import java.io.Serializable
+import kotlin.test.Test
+
+class TypeVariableNameTest {
+  @Test fun nullableAnyIsImplicitBound() {
+    val typeVariableName = TypeVariableName("T")
+    assertThat(typeVariableName.bounds).containsExactly(NULLABLE_ANY)
+  }
+
+  @Test fun oneTypeVariableNoBounds() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T"))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun <T> foo(): T? = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun twoTypeVariablesNoBounds() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T"))
+      .addTypeVariable(TypeVariableName("U"))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun <T, U> foo(): T? = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun oneTypeVariableOneBound() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T", Serializable::class))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun <T : java.io.Serializable> foo(): T? = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun twoTypeVariablesOneBoundEach() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T", Serializable::class))
+      .addTypeVariable(TypeVariableName("U", Runnable::class))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun <T : java.io.Serializable, U : java.lang.Runnable> foo(): T? = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun oneTypeVariableTwoBounds() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public fun <T> foo(): T? where T : java.io.Serializable, T : java.lang.Runnable = null
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun twoTypeVariablesTwoBoundsEach() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+      .addTypeVariable(TypeVariableName("U", Comparator::class, Cloneable::class))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      "public fun <T, U> foo(): " +
+        "T? where T : java.io.Serializable, T : java.lang.Runnable, " +
+        "U : java.util.Comparator, U : kotlin.Cloneable = null\n",
+    )
+  }
+
+  @Test fun threeTypeVariables() {
+    val funSpec = FunSpec.builder("foo")
+      .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+      .addTypeVariable(TypeVariableName("U", Cloneable::class))
+      .addTypeVariable(TypeVariableName("V"))
+      .returns(TypeVariableName("T").copy(nullable = true))
+      .addStatement("return null")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      "public fun <T, U : kotlin.Cloneable, V> foo(): " +
+        "T? where T : java.io.Serializable, T : java.lang.Runnable = null\n",
+    )
+  }
+
+  @Test fun addingBoundsRemovesImplicitBound() {
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("T").copy(bounds = listOf(Number::class.asTypeName())))
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<T : kotlin.Number>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inVariance() {
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("E", Number::class, variance = KModifier.IN))
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<in E : kotlin.Number>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun outVariance() {
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("E", Number::class, variance = KModifier.OUT))
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<out E : kotlin.Number>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun invalidVariance() {
+    assertThrows<IllegalArgumentException> {
+      TypeVariableName("E", KModifier.FINAL)
+    }
+  }
+
+  @Test fun reified() {
+    val funSpec = FunSpec.builder("printMembers")
+      .addModifiers(KModifier.INLINE)
+      .addTypeVariable(TypeVariableName("T").copy(reified = true))
+      .addStatement("println(T::class.members)")
+      .build()
+    assertThat(funSpec.toString()).isEqualTo(
+      """
+      |public inline fun <reified T> printMembers(): kotlin.Unit {
+      |  println(T::class.members)
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun anyBoundsIsLegal() {
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("E", ANY))
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<E : kotlin.Any>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun filterOutNullableAnyBounds() {
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(TypeVariableName("E", NULLABLE_ANY))
+      .build()
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<E>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun emptyBoundsShouldDefaultToAnyNullable() {
+    val typeVariable = TypeVariableName("E", bounds = *emptyArray<TypeName>())
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(typeVariable)
+      .build()
+    assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<E>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun noBoundsShouldDefaultToAnyNullable() {
+    val typeVariable = TypeVariableName("E")
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(typeVariable)
+      .build()
+    assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<E>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun genericClassNoBoundsShouldDefaultToAnyNullable() {
+    val typeVariable = TypeVariableName.get(GenericClass::class.java.typeParameters[0])
+    val typeSpec = TypeSpec.classBuilder("Taco")
+      .addTypeVariable(typeVariable)
+      .build()
+    assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public class Taco<T>
+      |
+      """.trimMargin(),
+    )
+  }
+
+  class GenericClass<T>
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt
new file mode 100644
index 0000000..a3ae52b
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 Google, 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.base.Charsets.UTF_8
+import com.google.common.collect.ImmutableSet
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
+import java.util.Locale
+import java.util.concurrent.atomic.AtomicReference
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.Processor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import javax.tools.DiagnosticCollector
+import javax.tools.JavaFileObject
+import kotlin.test.Ignore
+
+@Ignore("Not clear this test is useful to retain in the Kotlin world")
+class TypesEclipseTest : AbstractTypesTest() {
+  /**
+   * A [JUnit4] [Rule] that executes tests such that a instances of [Elements] and [Types] are
+   * available during execution.
+   *
+   * To use this rule in a test, just add the following field:
+   *
+   * ```java
+   *   public CompilationRule compilationRule = new CompilationRule();
+   * ```
+   *
+   * @author Gregory Kick
+   */
+  class CompilationRule : TestRule {
+    private var elements: Elements? = null
+    private var types: Types? = null
+
+    override fun apply(base: Statement, description: Description): Statement {
+      return object : Statement() {
+        override fun evaluate() {
+          val thrown = AtomicReference<Throwable>()
+          val successful = compile(
+            listOf(object : AbstractProcessor() {
+              override fun getSupportedSourceVersion() = SourceVersion.latest()
+
+              override fun getSupportedAnnotationTypes() = setOf("*")
+
+              @Synchronized override fun init(processingEnv: ProcessingEnvironment) {
+                super.init(processingEnv)
+                elements = processingEnv.elementUtils
+                types = processingEnv.typeUtils
+              }
+
+              override fun process(
+                annotations: Set<TypeElement>,
+                roundEnv: RoundEnvironment
+              ): Boolean {
+                // just run the test on the last round after compilation is over
+                if (roundEnv.processingOver()) {
+                  try {
+                    base.evaluate()
+                  } catch (e: Throwable) {
+                    thrown.set(e)
+                  }
+                }
+                return false
+              }
+            })
+          )
+          check(successful)
+          val t = thrown.get()
+          if (t != null) {
+            throw t
+          }
+        }
+      }
+    }
+
+    /**
+     * Returns the [Elements] instance associated with the current execution of the rule.
+     *
+     * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+     */
+    fun getElements() = elements!!
+
+    /**
+     * Returns the [Types] instance associated with the current execution of the rule.
+     *
+     * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+     */
+    fun getTypes() = types!!
+
+    private fun compile(processors: Iterable<Processor>): Boolean {
+      val compiler = EclipseCompiler()
+      val diagnosticCollector = DiagnosticCollector<JavaFileObject>()
+      val fileManager = compiler.getStandardFileManager(
+        diagnosticCollector, Locale.getDefault(), UTF_8
+      )
+      val task = compiler.getTask(
+        null,
+        fileManager,
+        diagnosticCollector,
+        ImmutableSet.of(),
+        ImmutableSet.of(TypesEclipseTest::class.java.canonicalName),
+        ImmutableSet.of()
+      )
+      task.setProcessors(processors)
+      return task.call()!!
+    }
+  }
+
+  @JvmField @Rule val compilation = CompilationRule()
+
+  override val elements: Elements
+    get() = compilation.getElements()
+
+  override val types: Types
+    get() = compilation.getTypes()
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt
new file mode 100644
index 0000000..43bdbb7
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 Google, 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.testing.compile.CompilationRule
+import org.junit.Rule
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.test.Ignore
+
+@Ignore("Not clear this test is useful to retain in the Kotlin world")
+class TypesTest : AbstractTypesTest() {
+  @JvmField @Rule val compilation = CompilationRule()
+
+  override val elements: Elements
+    get() = compilation.elements
+
+  override val types: Types
+    get() = compilation.types
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt
new file mode 100644
index 0000000..de3650b
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class UtilTest {
+  @Test fun characterLiteral() {
+    assertEquals("a", characterLiteralWithoutSingleQuotes('a'))
+    assertEquals("b", characterLiteralWithoutSingleQuotes('b'))
+    assertEquals("c", characterLiteralWithoutSingleQuotes('c'))
+    assertEquals("%", characterLiteralWithoutSingleQuotes('%'))
+    // common escapes
+    assertEquals("\\b", characterLiteralWithoutSingleQuotes('\b'))
+    assertEquals("\\t", characterLiteralWithoutSingleQuotes('\t'))
+    assertEquals("\\n", characterLiteralWithoutSingleQuotes('\n'))
+    assertEquals("\\u000c", characterLiteralWithoutSingleQuotes('\u000c'))
+    assertEquals("\\r", characterLiteralWithoutSingleQuotes('\r'))
+    assertEquals("\"", characterLiteralWithoutSingleQuotes('"'))
+    assertEquals("\\'", characterLiteralWithoutSingleQuotes('\''))
+    assertEquals("\\\\", characterLiteralWithoutSingleQuotes('\\'))
+    // octal escapes
+    assertEquals("\\u0000", characterLiteralWithoutSingleQuotes('\u0000'))
+    assertEquals("\\u0007", characterLiteralWithoutSingleQuotes('\u0007'))
+    assertEquals("?", characterLiteralWithoutSingleQuotes('\u003f'))
+    assertEquals("\\u007f", characterLiteralWithoutSingleQuotes('\u007f'))
+    assertEquals("¿", characterLiteralWithoutSingleQuotes('\u00bf'))
+    assertEquals("ÿ", characterLiteralWithoutSingleQuotes('\u00ff'))
+    // unicode escapes
+    assertEquals("\\u0000", characterLiteralWithoutSingleQuotes('\u0000'))
+    assertEquals("\\u0001", characterLiteralWithoutSingleQuotes('\u0001'))
+    assertEquals("\\u0002", characterLiteralWithoutSingleQuotes('\u0002'))
+    assertEquals("€", characterLiteralWithoutSingleQuotes('\u20AC'))
+    assertEquals("☃", characterLiteralWithoutSingleQuotes('\u2603'))
+    assertEquals("♠", characterLiteralWithoutSingleQuotes('\u2660'))
+    assertEquals("♣", characterLiteralWithoutSingleQuotes('\u2663'))
+    assertEquals("♥", characterLiteralWithoutSingleQuotes('\u2665'))
+    assertEquals("♦", characterLiteralWithoutSingleQuotes('\u2666'))
+    assertEquals("✵", characterLiteralWithoutSingleQuotes('\u2735'))
+    assertEquals("✺", characterLiteralWithoutSingleQuotes('\u273A'))
+    assertEquals("/", characterLiteralWithoutSingleQuotes('\uFF0F'))
+  }
+
+  @Test fun stringLiteral() {
+    stringLiteral("abc")
+    stringLiteral("♦♥♠♣")
+    stringLiteral("€\\t@\\t\${\'\$\'}", "€\t@\t$")
+    assertThat(stringLiteralWithQuotes("abc();\ndef();"))
+      .isEqualTo("\"\"\"\n|abc();\n|def();\n\"\"\".trimMargin()")
+    stringLiteral("This is \\\"quoted\\\"!", "This is \"quoted\"!")
+    stringLiteral("e^{i\\\\pi}+1=0", "e^{i\\pi}+1=0")
+    assertThat(stringLiteralWithQuotes("abc();\ndef();", isConstantContext = true))
+      .isEqualTo("\"abc();\\ndef();\"")
+  }
+
+  @Test fun legalIdentifiers() {
+    assertThat("foo".isIdentifier).isTrue()
+    assertThat("bAr1".isIdentifier).isTrue()
+    assertThat("1".isIdentifier).isFalse()
+    assertThat("♦♥♠♣".isIdentifier).isFalse()
+    assertThat("`♦♥♠♣`".isIdentifier).isTrue()
+    assertThat("`  ♣ !`".isIdentifier).isTrue()
+    assertThat("€".isIdentifier).isFalse()
+    assertThat("`€`".isIdentifier).isTrue()
+    assertThat("`1`".isIdentifier).isTrue()
+    assertThat("```".isIdentifier).isFalse()
+    assertThat("``".isIdentifier).isFalse()
+    assertThat("\n".isIdentifier).isFalse()
+    assertThat("`\n`".isIdentifier).isFalse()
+    assertThat("\r".isIdentifier).isFalse()
+    assertThat("`\r`".isIdentifier).isFalse()
+    assertThat("when".isIdentifier).isTrue()
+    assertThat("fun".isIdentifier).isTrue()
+    assertThat("".isIdentifier).isFalse()
+  }
+
+  @Test fun escapeNonJavaIdentifiers() {
+    assertThat("8startWithNumber".escapeIfNecessary()).isEqualTo("`8startWithNumber`")
+    assertThat("with-hyphen".escapeIfNecessary()).isEqualTo("`with-hyphen`")
+    assertThat("with space".escapeIfNecessary()).isEqualTo("`with·space`")
+    assertThat("with_unicode_punctuation\u2026".escapeIfNecessary()).isEqualTo("`with_unicode_punctuation\u2026`")
+  }
+
+  @Test fun escapeSpaceInName() {
+    val generated = FileSpec.builder("a", "b")
+      .addFunction(
+        FunSpec.builder("foo").apply {
+          addParameter("aaa bbb", typeNameOf<(Int) -> String>())
+          val arg = mutableListOf<String>()
+          addStatement(
+            StringBuilder().apply {
+              repeat(10) {
+                append("%N($it) + ")
+                arg += "aaa bbb"
+              }
+              append("%N(100)")
+              arg += "aaa bbb"
+            }.toString(),
+            *arg.toTypedArray(),
+          )
+        }.build(),
+      )
+      .build()
+      .toString()
+
+    val expectedOutput = """
+      package a
+
+      import kotlin.Function1
+      import kotlin.Int
+      import kotlin.String
+      import kotlin.Unit
+
+      public fun foo(`aaa bbb`: Function1<Int, String>): Unit {
+        `aaa bbb`(0) + `aaa bbb`(1) + `aaa bbb`(2) + `aaa bbb`(3) + `aaa bbb`(4) + `aaa bbb`(5) +
+            `aaa bbb`(6) + `aaa bbb`(7) + `aaa bbb`(8) + `aaa bbb`(9) + `aaa bbb`(100)
+      }
+
+    """.trimIndent()
+
+    assertThat(generated).isEqualTo(expectedOutput)
+  }
+
+  @Test fun escapeMultipleTimes() {
+    assertThat("A-\$B".escapeIfNecessary()).isEqualTo("`A-\$B`")
+  }
+
+  @Test fun escapeEscaped() {
+    assertThat("`A`".escapeIfNecessary()).isEqualTo("`A`")
+  }
+
+  private fun stringLiteral(string: String) = stringLiteral(string, string)
+
+  private fun stringLiteral(expected: String, value: String) =
+    assertEquals("\"$expected\"", stringLiteralWithQuotes(value))
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt
new file mode 100644
index 0000000..e6efa3f
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+package com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class ValueTypeSpecTest(private val useValue: Boolean) {
+
+  companion object {
+    @JvmStatic
+    @Parameterized.Parameters(name = "value={0}")
+    fun data(): Collection<Array<Any>> {
+      return listOf(
+        arrayOf(true),
+        arrayOf(false),
+      )
+    }
+  }
+
+  private val modifier = if (useValue) KModifier.VALUE else INLINE
+  private val modifierString = modifier.keyword
+
+  private fun classBuilder() = if (useValue) {
+    TypeSpec.valueClassBuilder("Guacamole")
+  } else {
+    TypeSpec.classBuilder("Guacamole")
+      .addModifiers(modifier)
+  }
+
+  @Test fun validInlineClass() {
+    val guacamole = classBuilder()
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("avacado", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("avacado", String::class)
+          .initializer("avacado")
+          .build(),
+      )
+      .build()
+
+    assertThat(guacamole.toString()).isEqualTo(
+      """
+      |public $modifierString class Guacamole(
+      |  public val avacado: kotlin.String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineClassWithInitBlock() {
+    val guacamole = classBuilder()
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("avacado", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("avacado", String::class)
+          .initializer("avacado")
+          .build(),
+      )
+      .addInitializerBlock(CodeBlock.EMPTY)
+      .build()
+
+    assertThat(guacamole.toString()).isEqualTo(
+      """
+      |public $modifierString class Guacamole(
+      |  public val avacado: kotlin.String,
+      |) {
+      |  init {
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  class InlineSuperClass
+
+  @Test fun inlineClassWithSuperClass() {
+    assertThrows<IllegalStateException> {
+      classBuilder()
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("avocado", String::class)
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("avocado", String::class)
+            .initializer("avocado")
+            .build(),
+        )
+        .superclass(InlineSuperClass::class)
+        .build()
+    }.hasMessageThat().isEqualTo("value/inline classes cannot have super classes")
+  }
+
+  interface InlineSuperInterface
+
+  @Test fun inlineClassInheritsFromInterface() {
+    val guacamole = classBuilder()
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("avocado", String::class)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("avocado", String::class)
+          .initializer("avocado")
+          .build(),
+      )
+      .addSuperinterface(InlineSuperInterface::class)
+      .build()
+
+    assertThat(guacamole.toString()).isEqualTo(
+      """
+      |public $modifierString class Guacamole(
+      |  public val avocado: kotlin.String,
+      |) : com.squareup.kotlinpoet.ValueTypeSpecTest.InlineSuperInterface
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineClassWithoutBackingProperty() {
+    assertThrows<IllegalArgumentException> {
+      classBuilder()
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("avocado", String::class)
+            .build(),
+        )
+        .addProperty("garlic", String::class)
+        .build()
+    }.hasMessageThat().isEqualTo("value/inline classes must have a single read-only (val) property parameter.")
+  }
+
+  @Test fun inlineClassWithoutProperties() {
+    assertThrows<IllegalStateException> {
+      classBuilder()
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("avocado", String::class)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("value/inline classes must have at least 1 property")
+  }
+
+  @Test fun inlineClassWithMutableProperties() {
+    assertThrows<IllegalStateException> {
+      classBuilder()
+        .primaryConstructor(
+          FunSpec.constructorBuilder()
+            .addParameter("avocado", String::class)
+            .build(),
+        )
+        .addProperty(
+          PropertySpec.builder("avocado", String::class)
+            .initializer("avocado")
+            .mutable()
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("value/inline classes must have a single read-only (val) property parameter.")
+  }
+
+  @Test
+  fun inlineClassWithPrivateConstructor() {
+    val guacamole = classBuilder()
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("avocado", String::class)
+          .addModifiers(PRIVATE)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("avocado", String::class)
+          .initializer("avocado")
+          .build(),
+      )
+      .build()
+
+    assertThat(guacamole.toString()).isEqualTo(
+      """
+      |public $modifierString class Guacamole private constructor(
+      |  public val avocado: kotlin.String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun inlineEnumClass() {
+    val guacamole = TypeSpec.enumBuilder("Foo")
+      .addModifiers(modifier)
+      .primaryConstructor(
+        FunSpec.constructorBuilder()
+          .addParameter("x", Int::class)
+          .build(),
+      )
+      .addEnumConstant(
+        "A",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 1)
+          .build(),
+      )
+      .addEnumConstant(
+        "B",
+        TypeSpec.anonymousClassBuilder()
+          .addSuperclassConstructorParameter("%L", 2)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("x", Int::class)
+          .initializer("x")
+          .build(),
+      )
+      .build()
+    assertThat(guacamole.toString()).isEqualTo(
+      """
+      |public enum $modifierString class Foo(
+      |  public val x: kotlin.Int,
+      |) {
+      |  A(1),
+      |  B(2),
+      |  ;
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt
new file mode 100644
index 0000000..f0b68a1
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+package com.squareup.kotlinpoet.jvm
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.assertThrows
+import java.io.IOException
+import kotlin.test.Test
+
+class JvmAnnotationsTest {
+
+  @Test fun jvmField() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .jvmField()
+              .initializer("%S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmField
+      |
+      |public class Taco {
+      |  @JvmField
+      |  public val foo: String = "foo"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmFieldConstructorParameter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .jvmField()
+              .initializer("foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmField
+      |
+      |public class Taco(
+      |  @JvmField
+      |  public val foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmStaticProperty() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addType(
+            TypeSpec.companionObjectBuilder()
+              .addProperty(
+                PropertySpec.builder("foo", String::class)
+                  .jvmStatic()
+                  .initializer("%S", "foo")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmStatic
+      |
+      |public class Taco {
+      |  public companion object {
+      |    @JvmStatic
+      |    public val foo: String = "foo"
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmStaticFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addType(
+            TypeSpec.companionObjectBuilder()
+              .addFunction(
+                FunSpec.builder("foo")
+                  .jvmStatic()
+                  .addStatement("return %S", "foo")
+                  .returns(String::class)
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmStatic
+      |
+      |public class Taco {
+      |  public companion object {
+      |    @JvmStatic
+      |    public fun foo(): String = "foo"
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmStaticGetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addType(
+            TypeSpec.companionObjectBuilder()
+              .addProperty(
+                PropertySpec.builder("foo", String::class)
+                  .getter(
+                    FunSpec.getterBuilder()
+                      .jvmStatic()
+                      .addStatement("return %S", "foo")
+                      .build(),
+                  )
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmStatic
+      |
+      |public class Taco {
+      |  public companion object {
+      |    public val foo: String
+      |      @JvmStatic
+      |      get() = "foo"
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmStaticSetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addType(
+            TypeSpec.companionObjectBuilder()
+              .addProperty(
+                PropertySpec.builder("foo", String::class.asTypeName())
+                  .mutable()
+                  .setter(
+                    FunSpec.setterBuilder()
+                      .jvmStatic()
+                      .addParameter("value", String::class)
+                      .build(),
+                  )
+                  .initializer("%S", "foo")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmStatic
+      |
+      |public class Taco {
+      |  public companion object {
+      |    public var foo: String = "foo"
+      |      @JvmStatic
+      |      set(`value`) {
+      |      }
+      |  }
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmStaticForbiddenOnConstructor() {
+    assertThrows<IllegalStateException> {
+      FunSpec.constructorBuilder()
+        .jvmStatic()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmStatic to a constructor!")
+  }
+
+  @Test fun throwsFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .throws(IOException::class, IllegalArgumentException::class)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.io.IOException
+      |import java.lang.IllegalArgumentException
+      |import kotlin.Unit
+      |import kotlin.jvm.Throws
+      |
+      |@Throws(
+      |  IOException::class,
+      |  IllegalArgumentException::class,
+      |)
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun throwsFunctionCustomException() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .throws(ClassName("com.squareup.tacos", "IllegalTacoException"))
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import kotlin.jvm.Throws
+      |
+      |@Throws(IllegalTacoException::class)
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun throwsPrimaryConstructor() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .throws(IOException::class)
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.io.IOException
+      |import kotlin.String
+      |import kotlin.jvm.Throws
+      |
+      |public class Taco @Throws(IOException::class) constructor(
+      |  foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun throwsGetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .getter(
+            FunSpec.getterBuilder()
+              .throws(IOException::class)
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.io.IOException
+      |import kotlin.String
+      |import kotlin.jvm.Throws
+      |
+      |public val foo: String
+      |  @Throws(IOException::class)
+      |  get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun throwsSetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .mutable()
+          .setter(
+            FunSpec.setterBuilder()
+              .throws(IOException::class)
+              .addParameter("value", String::class)
+              .addStatement("print(%S)", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import java.io.IOException
+      |import kotlin.String
+      |import kotlin.jvm.Throws
+      |
+      |public var foo: String
+      |  @Throws(IOException::class)
+      |  set(`value`) {
+      |    print("foo")
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmOverloadsFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .jvmOverloads()
+          .addParameter("bar", Int::class)
+          .addParameter(
+            ParameterSpec.builder("baz", String::class)
+              .defaultValue("%S", "baz")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.String
+      |import kotlin.Unit
+      |import kotlin.jvm.JvmOverloads
+      |
+      |@JvmOverloads
+      |public fun foo(bar: Int, baz: String = "baz"): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmOverloadsPrimaryConstructor() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .jvmOverloads()
+              .addParameter("bar", Int::class)
+              .addParameter(
+                ParameterSpec.builder("baz", String::class)
+                  .defaultValue("%S", "baz")
+                  .build(),
+              )
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.String
+      |import kotlin.jvm.JvmOverloads
+      |
+      |public class Taco @JvmOverloads constructor(
+      |  bar: Int,
+      |  baz: String = "baz",
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmOverloadsOnGetterForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.getterBuilder()
+        .jvmOverloads()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmOverloads to a getter!")
+  }
+
+  @Test fun jvmOverloadsOnSetterForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.setterBuilder()
+        .jvmOverloads()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmOverloads to a setter!")
+  }
+
+  @Test fun jvmNameFile() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .jvmName("TacoUtils")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .initializer("%S", "foo")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |@file:JvmName("TacoUtils")
+      |
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmName
+      |
+      |public val foo: String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmNameFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .jvmName("getFoo")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import kotlin.jvm.JvmName
+      |
+      |@JvmName("getFoo")
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmNameGetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .getter(
+            FunSpec.getterBuilder()
+              .jvmName("foo")
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmName
+      |
+      |public val foo: String
+      |  @JvmName("foo")
+      |  get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmNameSetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class.asTypeName())
+          .mutable()
+          .initializer("%S", "foo")
+          .setter(
+            FunSpec.setterBuilder()
+              .jvmName("foo")
+              .addParameter("value", String::class)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmName
+      |
+      |public var foo: String = "foo"
+      |  @JvmName("foo")
+      |  set(`value`) {
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmNameForbiddenOnConstructor() {
+    assertThrows<IllegalStateException> {
+      FunSpec.constructorBuilder()
+        .jvmName("notAConstructor")
+    }.hasMessageThat().isEqualTo("Can't apply @JvmName to a constructor!")
+  }
+
+  @Test fun jvmMultifileClass() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .jvmMultifileClass()
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .initializer("%S", "foo")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |@file:JvmMultifileClass
+      |
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmMultifileClass
+      |
+      |public val foo: String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmSuppressWildcardsClass() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .jvmSuppressWildcards()
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.jvm.JvmSuppressWildcards
+      |
+      |@JvmSuppressWildcards
+      |public class Taco
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmSuppressWildcardsFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .jvmSuppressWildcards(suppress = false)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import kotlin.jvm.JvmSuppressWildcards
+      |
+      |@JvmSuppressWildcards(suppress = false)
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmSuppressWildcardsOnConstructorForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.constructorBuilder()
+        .jvmSuppressWildcards()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a constructor!")
+  }
+
+  @Test fun jvmSuppressWildcardsOnGetterForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.getterBuilder()
+        .jvmSuppressWildcards()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a getter!")
+  }
+
+  @Test fun jvmSuppressWildcardsOnSetterForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.setterBuilder()
+        .jvmSuppressWildcards()
+    }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a setter!")
+  }
+
+  @Test fun jvmSuppressWildcardsProperty() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .jvmSuppressWildcards(suppress = false)
+          .initializer("%S", "foo")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmSuppressWildcards
+      |
+      |@JvmSuppressWildcards(suppress = false)
+      |public val foo: String = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmSuppressWildcardsType() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .addParameter(
+            "a",
+            List::class.asClassName()
+              .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.Unit
+      |import kotlin.collections.List
+      |import kotlin.jvm.JvmSuppressWildcards
+      |
+      |public fun foo(a: List<@JvmSuppressWildcards Int>): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmWildcardType() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .addParameter(
+            "a",
+            List::class.asClassName()
+              .parameterizedBy(Int::class.asTypeName().jvmWildcard()),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Int
+      |import kotlin.Unit
+      |import kotlin.collections.List
+      |import kotlin.jvm.JvmWildcard
+      |
+      |public fun foo(a: List<@JvmWildcard Int>): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun synchronizedFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .synchronized()
+          .addStatement("return %S", "foo")
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.jvm.Synchronized
+      |
+      |@Synchronized
+      |public fun foo() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun synchronizedGetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .getter(
+            FunSpec.getterBuilder()
+              .synchronized()
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Synchronized
+      |
+      |public val foo: String
+      |  @Synchronized
+      |  get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun synchronizedSetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class.asTypeName())
+          .mutable()
+          .initializer("%S", "foo")
+          .setter(
+            FunSpec.setterBuilder()
+              .synchronized()
+              .addParameter("value", String::class)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Synchronized
+      |
+      |public var foo: String = "foo"
+      |  @Synchronized
+      |  set(`value`) {
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun synchronizedOnConstructorForbidden() {
+    assertThrows<IllegalStateException> {
+      FunSpec.constructorBuilder()
+        .synchronized()
+    }.hasMessageThat().isEqualTo("Can't apply @Synchronized to a constructor!")
+  }
+
+  @Test fun transient() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .transient()
+              .initializer("%S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Transient
+      |
+      |public class Taco {
+      |  @Transient
+      |  public val foo: String = "foo"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun transientConstructorParameter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .transient()
+              .initializer("foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Transient
+      |
+      |public class Taco(
+      |  @Transient
+      |  public val foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun volatile() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .volatile()
+              .initializer("%S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Volatile
+      |
+      |public class Taco {
+      |  @Volatile
+      |  public val foo: String = "foo"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun volatileConstructorParameter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .volatile()
+              .initializer("foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Volatile
+      |
+      |public class Taco(
+      |  @Volatile
+      |  public val foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun strictfpFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addFunction(
+        FunSpec.builder("foo")
+          .strictfp()
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.Unit
+      |import kotlin.jvm.Strictfp
+      |
+      |@Strictfp
+      |public fun foo(): Unit {
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun strictfpPrimaryConstructor() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .strictfp()
+              .addParameter("foo", String::class)
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Strictfp
+      |
+      |public class Taco @Strictfp constructor(
+      |  foo: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun strictfpGetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .getter(
+            FunSpec.getterBuilder()
+              .strictfp()
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Strictfp
+      |
+      |public val foo: String
+      |  @Strictfp
+      |  get() = "foo"
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun strictfpSetter() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addProperty(
+        PropertySpec.builder("foo", String::class)
+          .mutable()
+          .setter(
+            FunSpec.setterBuilder()
+              .strictfp()
+              .addParameter("value", String::class)
+              .addStatement("print(%S)", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.Strictfp
+      |
+      |public var foo: String
+      |  @Strictfp
+      |  set(`value`) {
+      |    print("foo")
+      |  }
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmDefaultProperty() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.interfaceBuilder("Taco")
+          .addProperty(
+            PropertySpec.builder("foo", String::class)
+              .jvmDefault()
+              .initializer("%S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmDefault
+      |
+      |public interface Taco {
+      |  @JvmDefault
+      |  public val foo: String = "foo"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmDefaultFunction() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.interfaceBuilder("Taco")
+          .addFunction(
+            FunSpec.builder("foo")
+              .jvmDefault()
+              .returns(String::class)
+              .addStatement("return %S", "foo")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmDefault
+      |
+      |public interface Taco {
+      |  @JvmDefault
+      |  public fun foo(): String = "foo"
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmInlineClass() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.valueClassBuilder("Taco")
+          .jvmInline()
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("value", STRING)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("value", STRING)
+              .initializer("value")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmInline
+      |
+      |@JvmInline
+      |public value class Taco(
+      |  public val `value`: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun jvmRecordClass() {
+    val file = FileSpec.builder("com.squareup.tacos", "Taco")
+      .addType(
+        TypeSpec.classBuilder("Taco")
+          .jvmRecord()
+          .addModifiers(DATA)
+          .primaryConstructor(
+            FunSpec.constructorBuilder()
+              .addParameter("value", STRING)
+              .build(),
+          )
+          .addProperty(
+            PropertySpec.builder("value", STRING)
+              .initializer("value")
+              .build(),
+          )
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.squareup.tacos
+      |
+      |import kotlin.String
+      |import kotlin.jvm.JvmRecord
+      |
+      |@JvmRecord
+      |public data class Taco(
+      |  public val `value`: String,
+      |)
+      |
+      """.trimMargin(),
+    )
+  }
+}
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..74caf79
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,69 @@
+# pip install mkdocs mkdocs-material
+# mkdocs serve
+# mkdocs gh-deploy
+
+site_name: KotlinPoet
+repo_name: KotlinPoet
+repo_url: https://github.com/square/kotlinpoet
+site_description: "A Kotlin API for generating .kt source files"
+site_author: Square, Inc.
+remote_branch: gh-pages
+
+copyright: 'Copyright &copy; 2015 Square, Inc.'
+
+theme:
+  name: 'material'
+  logo: 'images/icon-square.png'
+  favicon: 'images/icon-square.png'
+  palette:
+    - media: '(prefers-color-scheme: light)'
+      scheme: default
+      primary: 'cyan'
+      accent: 'deep-purple'
+      toggle:
+        icon: material/weather-night
+        name: Switch to dark mode
+    - media: '(prefers-color-scheme: dark)'
+      scheme: slate
+      primary: 'black'
+      accent: 'blue-grey'
+      toggle:
+        icon: material/weather-sunny
+        name: Switch to light mode
+
+extra_css:
+  - 'css/app.css'
+
+markdown_extensions:
+  - smarty
+  - codehilite:
+      guess_lang: false
+  - footnotes
+  - meta
+  - toc:
+      permalink: true
+  - pymdownx.betterem:
+      smart_enable: all
+  - pymdownx.caret
+  - pymdownx.inlinehilite
+  - pymdownx.magiclink
+  - pymdownx.smartsymbols
+  - pymdownx.superfences
+  - pymdownx.emoji
+  - tables
+  - admonition
+
+nav:
+  - 'Overview':
+    - 'KotlinPoet': index.md
+    - 'Interop - JavaPoet': interop-javapoet.md
+    - 'Interop - kotlinx-metadata': interop-kotlinx-metadata.md
+    - 'Interop - KSP': interop-ksp.md
+  - 'API':
+    - 'kotlinpoet': 1.x/kotlinpoet/index.html
+    - 'interop-javapoet': 1.x/interop-javapoet/index.html
+    - 'interop-kotlinx-metadata': 1.x/interop-kotlinx-metadata/index.html
+    - 'interop-ksp': 1.x/interop-ksp/index.html
+  - 'Stack Overflow ⏏': https://stackoverflow.com/questions/tagged/kotlinpoet?sort=active
+  - 'Change Log': changelog.md
+  - 'Contributing': contributing.md
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..7ebea23
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,12 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": [
+    "config:base"
+  ],
+  "packageRules": [
+    {
+      "matchManagers": ["pip_requirements"],
+      "automerge": true
+    }
+  ]
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..3e23cc2
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+pluginManagement {
+  repositories {
+    mavenCentral()
+    gradlePluginPortal()
+  }
+}
+
+include(
+    ":kotlinpoet",
+    ":interop:javapoet",
+    ":interop:kotlinx-metadata",
+    ":interop:ksp",
+    ":interop:ksp:test-processor",
+)