Repository: square/kotlinpoet
Issue: Allow PropertySpec with context receivers and without getter or setter

Such code is valid in kotlin but fails to be generated by kotlinpoet with the error: `properties with context receivers require a getter`:

```kotlin
interface Bar {
   context(LoggingScope)
   val foo: Int
}
```

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

diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
index 8fb58602..4b059b16 100644
--- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
@@ -56,12 +56,6 @@ public class PropertySpec private constructor(
     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(
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
index 66b98369..ea299bc7 100644
--- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
@@ -789,6 +789,20 @@ public class TypeSpec private constructor(
         }
       }
 
+      for (propertySpec in propertySpecs) {
+        require(isAbstract || ABSTRACT !in propertySpec.modifiers) {
+          "non-abstract type $name cannot declare abstract property ${propertySpec.name}"
+        }
+        if (propertySpec.contextReceiverTypes.isNotEmpty()) {
+          if (ABSTRACT !in kind.implicitPropertyModifiers(modifiers) + propertySpec.modifiers) {
+            requireNotNull(propertySpec.getter) { "non-abstract properties with context receivers require a ${FunSpec.GETTER}" }
+            if (propertySpec.mutable) {
+              requireNotNull(propertySpec.setter) { "non-abstract mutable properties with context receivers require a ${FunSpec.SETTER}" }
+            }
+          }
+        }
+      }
+
       if (isAnnotation) {
         primaryConstructor?.let {
           requireNoneOf(it.modifiers, INTERNAL, PROTECTED, PRIVATE, ABSTRACT)
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
index 5eda0f78..af373c25 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
@@ -16,8 +16,6 @@
 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
@@ -650,44 +648,6 @@ class PropertySpecTest {
     )
   }
 
-  @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()
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
index 06a890cb..34b9e8f0 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
@@ -25,6 +25,7 @@ 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.SEALED
 import com.squareup.kotlinpoet.KModifier.VARARG
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.jvm.throws
@@ -5430,6 +5431,181 @@ class TypeSpecTest {
     assertThat(t).hasMessageThat().contains("contextReceivers can only be applied on simple classes")
   }
 
+  @Test fun valWithContextReceiverWithoutGetter() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Example")
+        .addProperty(
+          PropertySpec.builder("foo", STRING)
+            .mutable(false)
+            .contextReceivers(INT)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat()
+      .isEqualTo("non-abstract properties with context receivers require a get()")
+  }
+
+  @Test fun varWithContextReceiverWithoutAccessors() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Example")
+        .addProperty(
+          PropertySpec.builder("foo", STRING)
+            .mutable()
+            .contextReceivers(INT)
+            .getter(
+              FunSpec.getterBuilder()
+                .build(),
+            )
+            .build(),
+        ).build()
+    }.hasMessageThat()
+      .isEqualTo("non-abstract mutable properties with context receivers require a set()")
+
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Example")
+        .addProperty(
+          PropertySpec.builder("foo", STRING)
+            .mutable()
+            .contextReceivers(INT)
+            .setter(
+              FunSpec.setterBuilder()
+                .build(),
+            )
+            .build(),
+        ).build()
+    }.hasMessageThat()
+      .isEqualTo("non-abstract properties with context receivers require a get()")
+  }
+
+  // https://github.com/square/kotlinpoet/issues/1525
+  @Test fun propertyWithContextReceiverInInterface() {
+    val typeSpec = TypeSpec.interfaceBuilder("Bar")
+      .addProperty(
+        PropertySpec.builder("foo", Int::class)
+          .contextReceivers(STRING)
+          .build(),
+      )
+      .addProperty(
+        PropertySpec.builder("bar", Int::class)
+          .contextReceivers(STRING)
+          .mutable(true)
+          .build(),
+      )
+      .build()
+
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public interface Bar {
+      |  context(kotlin.String)
+      |  public val foo: kotlin.Int
+      |
+      |  context(kotlin.String)
+      |  public var bar: kotlin.Int
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun nonAbstractPropertyWithContextReceiverInAbstractClass() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Bar")
+        .addModifiers(ABSTRACT)
+        .addProperty(
+          PropertySpec.builder("foo", Int::class)
+            .contextReceivers(STRING)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("non-abstract properties with context receivers require a get()")
+  }
+
+  @Test fun abstractPropertyWithContextReceiverInAbstractClass() {
+    val typeSpec = TypeSpec.classBuilder("Bar")
+      .addModifiers(ABSTRACT)
+      .addProperty(
+        PropertySpec.builder("foo", Int::class)
+          .contextReceivers(STRING)
+          .addModifiers(ABSTRACT)
+          .build(),
+      )
+      .build()
+
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public abstract class Bar {
+      |  context(kotlin.String)
+      |  public abstract val foo: kotlin.Int
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun abstractPropertyInNonAbstractClass() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.classBuilder("Bar")
+        .addProperty(
+          PropertySpec.builder("foo", Int::class)
+            .addModifiers(ABSTRACT)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("non-abstract type Bar cannot declare abstract property foo")
+  }
+
+  @Test fun abstractPropertyInObject() {
+    assertThrows<IllegalArgumentException> {
+      TypeSpec.objectBuilder("Bar")
+        .addProperty(
+          PropertySpec.builder("foo", Int::class)
+            .addModifiers(ABSTRACT)
+            .build(),
+        )
+        .build()
+    }.hasMessageThat().isEqualTo("non-abstract type Bar cannot declare abstract property foo")
+  }
+
+  @Test fun abstractPropertyInEnum() {
+    val typeSpec = TypeSpec.enumBuilder("Bar")
+      .addProperty(
+        PropertySpec.builder("foo", Int::class)
+          .addModifiers(ABSTRACT)
+          .build(),
+      )
+      .build()
+
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public enum class Bar {
+      |  ;
+      |  public abstract val foo: kotlin.Int
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
+  @Test fun abstractPropertyInSealedClass() {
+    val typeSpec = TypeSpec.classBuilder("Bar")
+      .addModifiers(SEALED)
+      .addProperty(
+        PropertySpec.builder("foo", Int::class)
+          .addModifiers(ABSTRACT)
+          .build(),
+      )
+      .build()
+
+    assertThat(typeSpec.toString()).isEqualTo(
+      """
+      |public sealed class Bar {
+      |  public abstract val foo: kotlin.Int
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
   companion object {
     private const val donutsPackage = "com.squareup.donuts"
   }