From 396342017d307746852fd7923d2a414b79e2c8f3 Mon Sep 17 00:00:00 2001 From: seriouslyhypersonic Date: Sat, 7 May 2022 09:44:24 +0100 Subject: [PATCH 1/5] Fix context receiver and annotation order @FunSpec Prevent context receivers on accessors Add FunSpec tests: - Annotated function with context receiver - Accessor with context receiver Add context receivers to PropertySpec Add PropertySpec tests: - Var with context receiver - Val without getter with context receiver - Val with context receiver - Annotated val with context receiver --- .../java/com/squareup/kotlinpoet/FunSpec.kt | 3 +- .../com/squareup/kotlinpoet/PropertySpec.kt | 19 ++++++ .../com/squareup/kotlinpoet/FunSpecTest.kt | 30 +++++++++ .../squareup/kotlinpoet/PropertySpecTest.kt | 67 +++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 4d5f2428e7..2266f6dd79 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -87,8 +87,8 @@ public class FunSpec private constructor( } else { codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine()) } - codeWriter.emitAnnotations(annotations, false) codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n") + codeWriter.emitAnnotations(annotations, false) codeWriter.emitModifiers(modifiers, implicitModifiers) if (!isConstructor && !name.isAccessor) { @@ -368,6 +368,7 @@ public class FunSpec private constructor( @ExperimentalKotlinPoetApi public fun contextReceivers(receiverTypes: Iterable): Builder = apply { check(!name.isConstructor) { "constructors cannot have context receivers" } + check(!name.isAccessor) { "$name cannot have context receivers" } contextReceiverTypes += receiverTypes } diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt index 9d171154c0..591f1e8304 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt @@ -24,6 +24,7 @@ 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(), @@ -42,6 +43,9 @@ public class PropertySpec private constructor( public val setter: FunSpec? = builder.setter public val receiverType: TypeName? = builder.receiverType + @ExperimentalKotlinPoetApi + public val contextReceiverTypes: List = builder.contextReceiverTypes.toImmutableList() + init { require( typeVariables.none { it.isReified } || @@ -54,6 +58,10 @@ public class PropertySpec private constructor( require(mutable || setter == null) { "only a mutable property can have a setter" } + if (contextReceiverTypes.isNotEmpty()) { + check(!mutable) { "mutable properties cannot have context receivers" } + require(getter != null){ "immutable properties with context receivers require a $GETTER" } + } } internal fun emit( @@ -70,6 +78,7 @@ public class PropertySpec private constructor( 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Ā·") @@ -174,6 +183,7 @@ public class PropertySpec private constructor( internal var getter: FunSpec? = null internal var setter: FunSpec? = null internal var receiverType: TypeName? = null + internal val contextReceiverTypes: MutableList = mutableListOf() public val annotations: MutableList = mutableListOf() public val modifiers: MutableList = mutableListOf() @@ -270,6 +280,15 @@ public class PropertySpec private constructor( public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName()) + @ExperimentalKotlinPoetApi + public fun contextReceivers(receiverTypes: Iterable): Builder = apply { + contextReceiverTypes += receiverTypes + } + + @ExperimentalKotlinPoetApi + public fun contextReceivers(vararg receiverType: TypeName): Builder = + contextReceivers(receiverType.toList()) + public fun build(): PropertySpec { if (KModifier.INLINE in modifiers) { throw IllegalArgumentException( diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index 8804e7b3a2..df93cfcb6f 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -18,6 +18,8 @@ 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 org.junit.Rule import java.io.Closeable @@ -480,6 +482,22 @@ class FunSpecTest { ) } + @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") @@ -502,6 +520,18 @@ class FunSpecTest { }.hasMessageThat().isEqualTo("constructors cannot have context receivers") } + @Test fun accessorWithContextReceiver() { + assertThrows { + FunSpec.getterBuilder() + .contextReceivers(STRING) + }.hasMessageThat().isEqualTo("$GETTER cannot have context receivers") + + assertThrows { + FunSpec.setterBuilder() + .contextReceivers(STRING) + }.hasMessageThat().isEqualTo("$SETTER cannot have context receivers") + } + @Test fun functionParamSingleLambdaParam() { val unitType = UNIT val booleanType = BOOLEAN diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt index 2ab1e2448d..632ab43cc5 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt @@ -19,11 +19,15 @@ import com.google.common.truth.Truth.assertThat import com.squareup.kotlinpoet.KModifier.PRIVATE import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import java.io.Serializable +import java.lang.IllegalStateException 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() @@ -559,4 +563,67 @@ class PropertySpecTest { """.trimIndent() ) } + + @Test fun varWithcontextReceiver() { + assertThrows { + PropertySpec.builder("foo", STRING) + .mutable() + .contextReceivers(INT) + .build() + }.hasMessageThat().isEqualTo("mutable properties cannot have context receivers") + } + + @Test fun valWithoutGetterWithcontextReceiver() { + assertThrows { + PropertySpec.builder("foo", STRING) + .mutable(false) + .contextReceivers(INT) + .build() + }.hasMessageThat() + .isEqualTo("immutable properties with context receivers require a ${FunSpec.GETTER}") + } + + @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() + ) + } + + @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() + ) + } } From 12c60f321a8e0583c82bf1904ca5662cc5700f3e Mon Sep 17 00:00:00 2001 From: seriouslyhypersonic Date: Sat, 7 May 2022 14:04:48 +0100 Subject: [PATCH 2/5] Fix code style --- .../src/main/java/com/squareup/kotlinpoet/PropertySpec.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt index 591f1e8304..3bbd55cd95 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt @@ -60,7 +60,7 @@ public class PropertySpec private constructor( } if (contextReceiverTypes.isNotEmpty()) { check(!mutable) { "mutable properties cannot have context receivers" } - require(getter != null){ "immutable properties with context receivers require a $GETTER" } + require(getter != null) { "immutable properties with context receivers require a $GETTER" } } } From af5b869c13b97fff5440341a1c876b83e2339ae3 Mon Sep 17 00:00:00 2001 From: seriouslyhypersonic Date: Mon, 9 May 2022 10:12:22 +0100 Subject: [PATCH 3/5] Fix checks on var with context receivers and custom accessors Update tests for vars with context receivers without custom accessors Add test for var with context receivers and custom accessors Fix code style --- .../com/squareup/kotlinpoet/PropertySpec.kt | 7 +- .../squareup/kotlinpoet/PropertySpecTest.kt | 66 +++++++++++++++++-- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt index 3bbd55cd95..a77af9c2c4 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt @@ -59,8 +59,11 @@ public class PropertySpec private constructor( "only a mutable property can have a setter" } if (contextReceiverTypes.isNotEmpty()) { - check(!mutable) { "mutable properties cannot have context receivers" } - require(getter != null) { "immutable properties with context receivers require a $GETTER" } + if (mutable) { + requireNotNull(getter) { "mutable properties with context receivers require a $GETTER" } + requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" } + } + requireNotNull(getter) { "immutable properties with context receivers require a $GETTER" } } } diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt index 632ab43cc5..092970aa55 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt @@ -16,10 +16,11 @@ 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.PRIVATE import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import java.io.Serializable -import java.lang.IllegalStateException import java.util.function.Function import kotlin.reflect.KClass import kotlin.test.Test @@ -564,23 +565,75 @@ class PropertySpecTest { ) } - @Test fun varWithcontextReceiver() { - assertThrows { + @Test fun varWithContextReceiverWithoutCustomAccessors() { + val mutablePropertySpecBuilder = { PropertySpec.builder("foo", STRING) .mutable() .contextReceivers(INT) + } + + assertThrows { + mutablePropertySpecBuilder() + .getter( + FunSpec.getterBuilder() + .build() + ) .build() - }.hasMessageThat().isEqualTo("mutable properties cannot have context receivers") + }.hasMessageThat() + .isEqualTo("mutable properties with context receivers require a $SETTER") + + assertThrows { + mutablePropertySpecBuilder() + .setter( + FunSpec.setterBuilder() + .build() + ) + .build() + }.hasMessageThat() + .isEqualTo("mutable properties with context receivers require a $GETTER") } - @Test fun valWithoutGetterWithcontextReceiver() { + @Test fun valWithContextReceiverWithoutGetter() { assertThrows { PropertySpec.builder("foo", STRING) .mutable(false) .contextReceivers(INT) .build() }.hasMessageThat() - .isEqualTo("immutable properties with context receivers require a ${FunSpec.GETTER}") + .isEqualTo("immutable 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() { @@ -604,6 +657,7 @@ class PropertySpecTest { ) } + @OptIn(DelicateKotlinPoetApi::class) @Test fun annotatedValWithContextReceiver() { val propertySpec = PropertySpec.builder("foo", INT) .mutable(false) From dc49f40af9f272e4f81d967286d1dea57bb78108 Mon Sep 17 00:00:00 2001 From: Nuno Alves de Sousa <35973158+seriouslyhypersonic@users.noreply.github.com> Date: Mon, 9 May 2022 15:36:18 +0100 Subject: [PATCH 4/5] Update kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt Co-authored-by: Egor Andreevich --- .../src/main/java/com/squareup/kotlinpoet/PropertySpec.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt index a77af9c2c4..b69702f8f5 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt @@ -59,11 +59,10 @@ public class PropertySpec private constructor( "only a mutable property can have a setter" } if (contextReceiverTypes.isNotEmpty()) { + requireNotNull(getter) { "properties with context receivers require a $GETTER" } if (mutable) { - requireNotNull(getter) { "mutable properties with context receivers require a $GETTER" } requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" } } - requireNotNull(getter) { "immutable properties with context receivers require a $GETTER" } } } From a4366d4d18fce9ae58abf2a7e7d616de1d24bb1f Mon Sep 17 00:00:00 2001 From: seriouslyhypersonic Date: Mon, 9 May 2022 19:42:17 +0100 Subject: [PATCH 5/5] Update tests --- .../src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt index 092970aa55..ca80ba44ae 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt @@ -590,7 +590,7 @@ class PropertySpecTest { ) .build() }.hasMessageThat() - .isEqualTo("mutable properties with context receivers require a $GETTER") + .isEqualTo("properties with context receivers require a $GETTER") } @Test fun valWithContextReceiverWithoutGetter() { @@ -600,7 +600,7 @@ class PropertySpecTest { .contextReceivers(INT) .build() }.hasMessageThat() - .isEqualTo("immutable properties with context receivers require a $GETTER") + .isEqualTo("properties with context receivers require a $GETTER") } @Test fun varWithContextReceiver() {