From 1c353cf4782afbc04ea475d3369cd6183883ceee Mon Sep 17 00:00:00 2001 From: David Rawson Date: Fri, 26 May 2023 06:23:52 +1200 Subject: [PATCH] Fix #1525 --- .../com/squareup/kotlinpoet/PropertySpec.kt | 6 - .../java/com/squareup/kotlinpoet/TypeSpec.kt | 14 ++ .../squareup/kotlinpoet/PropertySpecTest.kt | 40 ---- .../com/squareup/kotlinpoet/TypeSpecTest.kt | 176 ++++++++++++++++++ 4 files changed, 190 insertions(+), 46 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt index 8fb5860262..4b059b16fa 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 66b98369ac..ea299bc77f 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 5eda0f785c..af373c2511 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 { - mutablePropertySpecBuilder() - .getter( - FunSpec.getterBuilder() - .build(), - ) - .build() - }.hasMessageThat() - .isEqualTo("mutable properties with context receivers require a $SETTER") - - assertThrows { - mutablePropertySpecBuilder() - .setter( - FunSpec.setterBuilder() - .build(), - ) - .build() - }.hasMessageThat() - .isEqualTo("properties with context receivers require a $GETTER") - } - - @Test fun valWithContextReceiverWithoutGetter() { - assertThrows { - 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 06a890cba1..34b9e8f074 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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" }