Repository: square/kotlinpoet
Issue: Adding type variable to property

Kotlin: 1.2.60
KotlinPoet: 1.0.0-RC1

I'm trying to create something akin to:

```kotlin
private inline val <T : Any> KClass<T>.someFunction
    get() = stuff as T
```

I have this so far:
```kotlin
val prop = PropertySpec.builder("someFunction", TypeVariableName("T"), KModifier.PRIVATE)
    .receiver(KClass::class.asTypeName().parameterizedBy(TypeVariableName("T")))
    .getter(
        FunSpec.getterBuilder()
            .addModifiers(KModifier.INLINE)
            .addStatement("return %L", "stuff as T")
            .build()
    )
    .build()
```

Which generates:
```kotlin
private val kotlin.reflect.KClass<T>.someFunction: T
    inline get() = stuff as T
```

But without the property type variable `T` is unresolved. I tried putting it on the getter `FunSpec` but it was disallowed for getters (not surprising).

I would expect something like `.addTypeVariable(TypeVariableName("T", ANY))` to be available on the `PropertySpec` builder (or I'm just blind and missed something).


Changed files:
src/main/java/com/squareup/kotlinpoet/PropertySpec.kt, src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt

diff --git a/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
index e25199c0..cec967c4 100644
--- a/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
+++ b/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
@@ -28,12 +28,22 @@ class PropertySpec private constructor(builder: Builder) {
   val kdoc = builder.kdoc.build()
   val annotations = builder.annotations.toImmutableList()
   val modifiers = builder.modifiers.toImmutableSet()
+  val typeVariables = builder.typeVariables.toImmutableList()
   val initializer = builder.initializer
   val delegated = builder.delegated
   val getter = builder.getter
   val setter = builder.setter
   val receiverType = builder.receiverType
 
+  init {
+    require(typeVariables.none { it.reified } ||
+        (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!"
+    }
+  }
+
   internal fun emit(
     codeWriter: CodeWriter,
     implicitModifiers: Set<KModifier>,
@@ -49,6 +59,10 @@ class PropertySpec private constructor(builder: Builder) {
     codeWriter.emitAnnotations(annotations, inline)
     codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
     codeWriter.emit(if (mutable) "var " else "val ")
+    if (typeVariables.isNotEmpty()) {
+      codeWriter.emitTypeVariables(typeVariables)
+      codeWriter.emit(" ")
+    }
     if (receiverType != null) {
       if (receiverType is LambdaTypeName) {
         codeWriter.emitCode("(%T).", receiverType)
@@ -65,6 +79,7 @@ class PropertySpec private constructor(builder: Builder) {
       }
       codeWriter.emitCode("%[%L%]", initializer)
     }
+    codeWriter.emitWhereBlock(typeVariables)
     if (!inline) codeWriter.emit("\\n")
     val implicitAccessorModifiers = if (isInlineProperty) {
       implicitModifiers + KModifier.INLINE
@@ -100,6 +115,7 @@ class PropertySpec private constructor(builder: Builder) {
     builder.kdoc.add(kdoc)
     builder.annotations += annotations
     builder.modifiers += modifiers
+    builder.typeVariables += typeVariables
     builder.initializer = initializer
     builder.delegated = delegated
     builder.setter = setter
@@ -119,6 +135,7 @@ class PropertySpec private constructor(builder: Builder) {
 
     val annotations = mutableListOf<AnnotationSpec>()
     val modifiers = mutableListOf<KModifier>()
+    val typeVariables = mutableListOf<TypeVariableName>()
 
     fun mutable(mutable: Boolean) = apply {
       this.mutable = mutable
@@ -152,6 +169,14 @@ class PropertySpec private constructor(builder: Builder) {
       this.modifiers += modifiers
     }
 
+    fun addTypeVariables(typeVariables: Iterable<TypeVariableName>) = apply {
+      this.typeVariables += typeVariables
+    }
+
+    fun addTypeVariable(typeVariable: TypeVariableName) = apply {
+      typeVariables += typeVariable
+    }
+
     fun initializer(format: String, vararg args: Any?) = initializer(CodeBlock.of(format, *args))
 
     fun initializer(codeBlock: CodeBlock) = apply {
diff --git a/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
index 26eae92a..5fbbf22f 100644
--- a/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
+++ b/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
@@ -16,6 +16,10 @@
 package com.squareup.kotlinpoet
 
 import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Serializable
+import java.util.function.Function
+import kotlin.reflect.KClass
 import kotlin.test.Test
 
 class PropertySpecTest {
@@ -112,6 +116,7 @@ class PropertySpecTest {
         .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()
@@ -153,4 +158,106 @@ class PropertySpecTest {
 
     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, KModifier.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 val <T : kotlin.Any> kotlin.reflect.KClass<T>.someFunction: T
+      |    inline 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, KModifier.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").reified(true)
+    val prop = PropertySpec.builder("someFunction", t, KModifier.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 val <reified T> kotlin.reflect.KClass<T>.someFunction: T
+      |    inline get() = stuff as T
+      |""".trimMargin())
+  }
+
+  @Test fun reifiedTypeVariableNotAllowedWhenNoAccessors() {
+    assertThrows<IllegalArgumentException> {
+      PropertySpec.builder("property", String::class)
+          .addTypeVariable(TypeVariableName("T").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").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.varBuilder("property", String::class)
+          .addTypeVariable(TypeVariableName("T").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.varBuilder("property", String::class)
+          .addTypeVariable(TypeVariableName("T").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!")
+  }
 }