From ebd4464d1a0870b9635485bdac8f5d4b30ede730 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Apr 2022 23:03:26 +0200 Subject: [PATCH 01/21] Add API for context receivers --- .../squareup/kotlinpoet/ContextReceivers.kt | 10 +++ .../java/com/squareup/kotlinpoet/FunSpec.kt | 62 ++++++++++++++----- .../com/squareup/kotlinpoet/LambdaTypeName.kt | 18 +++++- 3 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt new file mode 100644 index 0000000000..1fa4e6d5ca --- /dev/null +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt @@ -0,0 +1,10 @@ +package com.squareup.kotlinpoet + +/** + * Annotation marking APIs for the [KEEP-259](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md) + * prototype in Kotlin 1.6.20 + */ +@RequiresOptIn +@MustBeDocumented +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY) +public annotation class ContextReceivers diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 671bcfded7..f265d88cb6 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:OptIn(ContextReceivers::class) 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 com.squareup.kotlinpoet.KModifier.* import java.lang.reflect.Type import javax.lang.model.element.Element import javax.lang.model.element.ExecutableElement @@ -45,6 +42,8 @@ public class FunSpec private constructor( public val modifiers: Set = builder.modifiers.toImmutableSet() public val typeVariables: List = builder.typeVariables.toImmutableList() public val receiverType: TypeName? = builder.receiverType + @ContextReceivers + public val contextReceiverTypes: List = builder.contextReceiverTypes.toImmutableList() public val returnType: TypeName? = builder.returnType public val parameters: List = builder.parameters.toImmutableList() public val delegateConstructor: String? = builder.delegateConstructor @@ -285,6 +284,7 @@ public class FunSpec private constructor( internal var returnKdoc = CodeBlock.EMPTY internal var receiverKdoc = CodeBlock.EMPTY internal var receiverType: TypeName? = null + internal val contextReceiverTypes: MutableList = mutableListOf() internal var returnType: TypeName? = null internal var delegateConstructor: String? = null internal var delegateConstructorArguments = listOf() @@ -359,7 +359,30 @@ public class FunSpec private constructor( typeVariables += typeVariable } - @JvmOverloads public fun receiver( + public fun contextReceiver(receiverTypes: Collection): Builder = apply { + check(!name.isConstructor) { "$name cannot have receiver type" } + contextReceiverTypes += receiverTypes + } + public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) + + @ContextReceivers + @JvmName("contextReceiverByType") + public fun contextReceiver(receiverTypes: List): Builder = + contextReceiver(receiverTypes.map { it.asTypeName() }) + @ContextReceivers + public fun contextReceiver(vararg receiverType: Type): Builder = + contextReceiver(receiverType.toList()) + + @ContextReceivers + @JvmName("contextReceiverKClass") + public fun contextReceiver(receiverTypes: List>): Builder = + contextReceiver(receiverTypes.map { it.asTypeName() }) + @ContextReceivers + public fun contextReceiver(vararg receiverType: KClass<*>): Builder = + contextReceiver(receiverType.toList()) + + @JvmOverloads + public fun receiver( receiverType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = apply { @@ -368,7 +391,8 @@ public class FunSpec private constructor( this.receiverKdoc = kdoc } - @JvmOverloads public fun receiver( + @JvmOverloads + public fun receiver( receiverType: Type, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = receiver(receiverType.asTypeName(), kdoc) @@ -379,7 +403,8 @@ public class FunSpec private constructor( vararg args: Any ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args)) - @JvmOverloads public fun receiver( + @JvmOverloads + public fun receiver( receiverType: KClass<*>, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = receiver(receiverType.asTypeName(), kdoc) @@ -390,7 +415,8 @@ public class FunSpec private constructor( vararg args: Any ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args)) - @JvmOverloads public fun returns( + @JvmOverloads + public fun returns( returnType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = apply { @@ -399,13 +425,15 @@ public class FunSpec private constructor( this.returnKdoc = kdoc } - @JvmOverloads public fun returns(returnType: Type, kdoc: CodeBlock = CodeBlock.EMPTY): Builder = + @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( + @JvmOverloads + public fun returns( returnType: KClass<*>, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = returns(returnType.asTypeName(), kdoc) @@ -547,13 +575,17 @@ public class FunSpec private constructor( 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 builder(name: String): Builder = Builder(name) - @JvmStatic public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR) + @JvmStatic + public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR) - @JvmStatic public fun getterBuilder(): Builder = Builder(GETTER) + @JvmStatic + public fun getterBuilder(): Builder = Builder(GETTER) - @JvmStatic public fun setterBuilder(): Builder = Builder(SETTER) + @JvmStatic + public fun setterBuilder(): Builder = Builder(SETTER) @DelicateKotlinPoetApi( message = "Element APIs don't give complete information on Kotlin types. Consider using" + diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 3f8514a3b7..79c51dc141 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -17,8 +17,11 @@ package com.squareup.kotlinpoet import kotlin.reflect.KClass +@OptIn(ContextReceivers::class) public class LambdaTypeName private constructor( public val receiver: TypeName? = null, + @property:ContextReceivers + public val contextReceivers: List? = emptyList(), parameters: List = emptyList(), public val returnType: TypeName = UNIT, nullable: Boolean = false, @@ -50,7 +53,7 @@ public class LambdaTypeName private constructor( suspending: Boolean = this.isSuspending, tags: Map, Any> = this.tags.toMap() ): LambdaTypeName { - return LambdaTypeName(receiver, parameters, returnType, nullable, suspending, annotations, tags) + return LambdaTypeName(receiver, contextReceivers, parameters, returnType, nullable, suspending, annotations, tags) } override fun emit(out: CodeWriter): CodeWriter { @@ -80,12 +83,20 @@ public class LambdaTypeName private constructor( } public companion object { + /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ + @ContextReceivers @JvmStatic public fun get( + receiver: TypeName? = null, + contextReceivers: List = emptyList(), + parameters: List = emptyList(), + returnType: TypeName + ): 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 = emptyList(), returnType: TypeName - ): LambdaTypeName = LambdaTypeName(receiver, parameters, returnType) + ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters, returnType) /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ @JvmStatic public fun get( @@ -95,6 +106,7 @@ public class LambdaTypeName private constructor( ): LambdaTypeName { return LambdaTypeName( receiver, + emptyList(), parameters.toList().map { ParameterSpec.unnamed(it) }, returnType ) @@ -105,6 +117,6 @@ public class LambdaTypeName private constructor( receiver: TypeName? = null, vararg parameters: ParameterSpec = emptyArray(), returnType: TypeName - ): LambdaTypeName = LambdaTypeName(receiver, parameters.toList(), returnType) + ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters.toList(), returnType) } } From f0d23a2202f2e9744ff33bd3b40445c7949ba0e6 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Apr 2022 23:51:37 +0200 Subject: [PATCH 02/21] Use existing opt-in annotation and make context-receivers not nullable --- .../java/com/squareup/kotlinpoet/ContextReceivers.kt | 10 ---------- .../src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 11 +++++------ .../java/com/squareup/kotlinpoet/LambdaTypeName.kt | 8 ++++---- 3 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt deleted file mode 100644 index 1fa4e6d5ca..0000000000 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivers.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squareup.kotlinpoet - -/** - * Annotation marking APIs for the [KEEP-259](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md) - * prototype in Kotlin 1.6.20 - */ -@RequiresOptIn -@MustBeDocumented -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY) -public annotation class ContextReceivers diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index f265d88cb6..724d75cd22 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -42,7 +42,7 @@ public class FunSpec private constructor( public val modifiers: Set = builder.modifiers.toImmutableSet() public val typeVariables: List = builder.typeVariables.toImmutableList() public val receiverType: TypeName? = builder.receiverType - @ContextReceivers + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public val contextReceiverTypes: List = builder.contextReceiverTypes.toImmutableList() public val returnType: TypeName? = builder.returnType public val parameters: List = builder.parameters.toImmutableList() @@ -365,23 +365,22 @@ public class FunSpec private constructor( } public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) - @ContextReceivers + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") @JvmName("contextReceiverByType") public fun contextReceiver(receiverTypes: List): Builder = contextReceiver(receiverTypes.map { it.asTypeName() }) - @ContextReceivers + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public fun contextReceiver(vararg receiverType: Type): Builder = contextReceiver(receiverType.toList()) - @ContextReceivers + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") @JvmName("contextReceiverKClass") public fun contextReceiver(receiverTypes: List>): Builder = contextReceiver(receiverTypes.map { it.asTypeName() }) - @ContextReceivers + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public fun contextReceiver(vararg receiverType: KClass<*>): Builder = contextReceiver(receiverType.toList()) - @JvmOverloads public fun receiver( receiverType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 79c51dc141..3b9e725f1f 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -17,11 +17,11 @@ package com.squareup.kotlinpoet import kotlin.reflect.KClass -@OptIn(ContextReceivers::class) +@OptIn(DelicateKotlinPoetApi::class) public class LambdaTypeName private constructor( public val receiver: TypeName? = null, - @property:ContextReceivers - public val contextReceivers: List? = emptyList(), + @property:DelicateKotlinPoetApi("Context receivers are currently a preview feature") + public val contextReceivers: List = emptyList(), parameters: List = emptyList(), public val returnType: TypeName = UNIT, nullable: Boolean = false, @@ -84,7 +84,7 @@ public class LambdaTypeName private constructor( public companion object { /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ - @ContextReceivers @JvmStatic public fun get( + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") @JvmStatic public fun get( receiver: TypeName? = null, contextReceivers: List = emptyList(), parameters: List = emptyList(), From fe97e3f9d9fda36f6dc189cf3c73087bfc1bfda7 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Apr 2022 23:56:48 +0200 Subject: [PATCH 03/21] Remove collection overloads --- .../java/com/squareup/kotlinpoet/FunSpec.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 724d75cd22..5888f78d1b 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ContextReceivers::class) package com.squareup.kotlinpoet import com.squareup.kotlinpoet.KModifier.* @@ -29,6 +28,7 @@ import kotlin.DeprecationLevel.WARNING import kotlin.reflect.KClass /** A generated function declaration. */ +@OptIn(DelicateKotlinPoetApi::class) public class FunSpec private constructor( builder: Builder, private val tagMap: TagMap = builder.buildTagMap(), @@ -42,6 +42,7 @@ public class FunSpec private constructor( public val modifiers: Set = builder.modifiers.toImmutableSet() public val typeVariables: List = builder.typeVariables.toImmutableList() public val receiverType: TypeName? = builder.receiverType + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public val contextReceiverTypes: List = builder.contextReceiverTypes.toImmutableList() public val returnType: TypeName? = builder.returnType @@ -359,27 +360,22 @@ public class FunSpec private constructor( typeVariables += typeVariable } + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public fun contextReceiver(receiverTypes: Collection): Builder = apply { check(!name.isConstructor) { "$name cannot have receiver type" } contextReceiverTypes += receiverTypes } - public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) @DelicateKotlinPoetApi("Context receivers are currently a preview feature") - @JvmName("contextReceiverByType") - public fun contextReceiver(receiverTypes: List): Builder = - contextReceiver(receiverTypes.map { it.asTypeName() }) + public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) + @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public fun contextReceiver(vararg receiverType: Type): Builder = - contextReceiver(receiverType.toList()) + contextReceiver(receiverType.map { it.asTypeName() }) @DelicateKotlinPoetApi("Context receivers are currently a preview feature") - @JvmName("contextReceiverKClass") - public fun contextReceiver(receiverTypes: List>): Builder = - contextReceiver(receiverTypes.map { it.asTypeName() }) - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") public fun contextReceiver(vararg receiverType: KClass<*>): Builder = - contextReceiver(receiverType.toList()) + contextReceiver(receiverType.map { it.asTypeName() }) public fun receiver( receiverType: TypeName, From 37aef5edfcad3345ad430da974402e6a67996f59 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Tue, 12 Apr 2022 23:59:56 +0200 Subject: [PATCH 04/21] Revert unwanted code style changes --- .../java/com/squareup/kotlinpoet/FunSpec.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 5888f78d1b..d3b1d244e6 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -15,7 +15,11 @@ */ package com.squareup.kotlinpoet -import com.squareup.kotlinpoet.KModifier.* +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 @@ -386,8 +390,7 @@ public class FunSpec private constructor( this.receiverKdoc = kdoc } - @JvmOverloads - public fun receiver( + @JvmOverloads public fun receiver( receiverType: Type, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = receiver(receiverType.asTypeName(), kdoc) @@ -398,8 +401,7 @@ public class FunSpec private constructor( vararg args: Any ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args)) - @JvmOverloads - public fun receiver( + @JvmOverloads public fun receiver( receiverType: KClass<*>, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = receiver(receiverType.asTypeName(), kdoc) @@ -410,8 +412,7 @@ public class FunSpec private constructor( vararg args: Any ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args)) - @JvmOverloads - public fun returns( + @JvmOverloads public fun returns( returnType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = apply { @@ -420,15 +421,13 @@ public class FunSpec private constructor( this.returnKdoc = kdoc } - @JvmOverloads - public fun returns(returnType: Type, kdoc: CodeBlock = CodeBlock.EMPTY): Builder = + @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( + @JvmOverloads public fun returns( returnType: KClass<*>, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = returns(returnType.asTypeName(), kdoc) @@ -570,17 +569,13 @@ public class FunSpec private constructor( 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 builder(name: String): Builder = Builder(name) - @JvmStatic - public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR) + @JvmStatic public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR) - @JvmStatic - public fun getterBuilder(): Builder = Builder(GETTER) + @JvmStatic public fun getterBuilder(): Builder = Builder(GETTER) - @JvmStatic - public fun setterBuilder(): Builder = Builder(SETTER) + @JvmStatic public fun setterBuilder(): Builder = Builder(SETTER) @DelicateKotlinPoetApi( message = "Element APIs don't give complete information on Kotlin types. Consider using" + From a505feb3e309501d9e3945864797f49b8fd206c4 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 00:01:43 +0200 Subject: [PATCH 05/21] Add wrongly remove @JvmOverloads annotation --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- 1 file changed, 1 insertion(+), 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 d3b1d244e6..76bcb40396 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -381,7 +381,7 @@ public class FunSpec private constructor( public fun contextReceiver(vararg receiverType: KClass<*>): Builder = contextReceiver(receiverType.map { it.asTypeName() }) - public fun receiver( + @JvmOverloads public fun receiver( receiverType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY ): Builder = apply { From f5f0784af050f6df84fd6c1acb24ef9690459318 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 00:39:21 +0200 Subject: [PATCH 06/21] Add code generator and tests --- .../main/java/com/squareup/kotlinpoet/FunSpec.kt | 4 ++++ .../java/com/squareup/kotlinpoet/LambdaTypeName.kt | 4 ++++ .../java/com/squareup/kotlinpoet/FunSpecTest.kt | 14 ++++++++++++++ .../com/squareup/kotlinpoet/LambdaTypeNameTest.kt | 12 ++++++++++++ 4 files changed, 34 insertions(+) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 76bcb40396..e5b49452a2 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -88,6 +88,10 @@ public class FunSpec private constructor( codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine()) } codeWriter.emitAnnotations(annotations, false) + if (contextReceiverTypes.isNotEmpty()) { + val receivers = contextReceiverTypes.joinToString(", ") { "%T" } + codeWriter.emitCode("context($receivers)·", *contextReceiverTypes.toTypedArray()) + } codeWriter.emitModifiers(modifiers, implicitModifiers) if (!isConstructor && !name.isAccessor) { diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 3b9e725f1f..28a6fdb53e 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -57,6 +57,10 @@ public class LambdaTypeName private constructor( } override fun emit(out: CodeWriter): CodeWriter { + if (contextReceivers.isNotEmpty()) { + val receivers = contextReceivers.joinToString(", ") { "%T" } + out.emitCode("context($receivers)·", *contextReceivers.toTypedArray()) + } if (isNullable) { out.emit("(") } diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index 13857a6f1f..b3871890c1 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -429,6 +429,20 @@ class FunSpecTest { ) } + @Test fun functionWithContextReceiver() { + val stringType = STRING + val funSpec = FunSpec.builder("foo") + .contextReceiver(stringType) + .build() + + assertThat(funSpec.toString()).isEqualTo( + """ + |context(kotlin.String) public fun foo(): kotlin.Unit { + |} + |""".trimMargin() + ) + } + @Test fun functionParamSingleLambdaParam() { val unitType = UNIT val booleanType = BOOLEAN diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt index e86d1f2b1b..89f9c7b069 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt @@ -51,6 +51,18 @@ class LambdaTypeNameTest { ) } + @Test fun contextReceiver() { + val typeName = LambdaTypeName.get( + Int::class.asTypeName(), + listOf(STRING), + listOf(), + Unit::class.asTypeName() + ) + assertThat(typeName.toString()).isEqualTo( + "context(kotlin.String) kotlin.Int.() -> kotlin.Unit" + ) + } + @Test fun paramsWithAnnotationsForbidden() { assertThrows { LambdaTypeName.get( From 6350d544517bc1bc64b8dd46b22de59e4802aa46 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 01:32:46 +0200 Subject: [PATCH 07/21] Apply requested changes - Use proper experimental annotation - Remove not needed overloads - Move contextReceivers parameter to the end --- .../kotlinpoet/ExperimentalKotlinPoetApi.kt | 15 +++++++++++++++ .../main/java/com/squareup/kotlinpoet/FunSpec.kt | 16 ++++------------ .../com/squareup/kotlinpoet/LambdaTypeName.kt | 10 +++++----- 3 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt 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 0000000000..7a4377c9c1 --- /dev/null +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt @@ -0,0 +1,15 @@ +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 part of an experimental KotlinPoet and is + * subject to API changes. + */ +@RequiresOptIn +@Retention(AnnotationRetention.BINARY) +@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS) +public annotation class ExperimentalKotlinPoetApi diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index e5b49452a2..9415639a44 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -32,7 +32,7 @@ import kotlin.DeprecationLevel.WARNING import kotlin.reflect.KClass /** A generated function declaration. */ -@OptIn(DelicateKotlinPoetApi::class) +@OptIn(ExperimentalKotlinPoetApi::class) public class FunSpec private constructor( builder: Builder, private val tagMap: TagMap = builder.buildTagMap(), @@ -47,7 +47,7 @@ public class FunSpec private constructor( public val typeVariables: List = builder.typeVariables.toImmutableList() public val receiverType: TypeName? = builder.receiverType - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") + @ExperimentalKotlinPoetApi public val contextReceiverTypes: List = builder.contextReceiverTypes.toImmutableList() public val returnType: TypeName? = builder.returnType public val parameters: List = builder.parameters.toImmutableList() @@ -368,23 +368,15 @@ public class FunSpec private constructor( typeVariables += typeVariable } - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") + @ExperimentalKotlinPoetApi public fun contextReceiver(receiverTypes: Collection): Builder = apply { check(!name.isConstructor) { "$name cannot have receiver type" } contextReceiverTypes += receiverTypes } - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") + @ExperimentalKotlinPoetApi public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") - public fun contextReceiver(vararg receiverType: Type): Builder = - contextReceiver(receiverType.map { it.asTypeName() }) - - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") - public fun contextReceiver(vararg receiverType: KClass<*>): Builder = - contextReceiver(receiverType.map { it.asTypeName() }) - @JvmOverloads public fun receiver( receiverType: TypeName, kdoc: CodeBlock = CodeBlock.EMPTY diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 28a6fdb53e..cd7080ff03 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -17,10 +17,10 @@ package com.squareup.kotlinpoet import kotlin.reflect.KClass -@OptIn(DelicateKotlinPoetApi::class) +@OptIn(ExperimentalKotlinPoetApi::class) public class LambdaTypeName private constructor( public val receiver: TypeName? = null, - @property:DelicateKotlinPoetApi("Context receivers are currently a preview feature") + @property:ExperimentalKotlinPoetApi public val contextReceivers: List = emptyList(), parameters: List = emptyList(), public val returnType: TypeName = UNIT, @@ -88,11 +88,11 @@ public class LambdaTypeName private constructor( public companion object { /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ - @DelicateKotlinPoetApi("Context receivers are currently a preview feature") @JvmStatic public fun get( + @ExperimentalKotlinPoetApi @JvmStatic public fun get( receiver: TypeName? = null, - contextReceivers: List = emptyList(), parameters: List = emptyList(), - returnType: TypeName + returnType: TypeName, + contextReceivers: List = emptyList() ): LambdaTypeName = LambdaTypeName(receiver, contextReceivers, parameters, returnType) /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ From 1e0f84794426a541743dec2081a7572a3aac6d5c Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 01:50:05 +0200 Subject: [PATCH 08/21] Update kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt Co-authored-by: Zac Sweers --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- 1 file changed, 1 insertion(+), 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 9415639a44..d32cd9706f 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -370,7 +370,7 @@ public class FunSpec private constructor( @ExperimentalKotlinPoetApi public fun contextReceiver(receiverTypes: Collection): Builder = apply { - check(!name.isConstructor) { "$name cannot have receiver type" } + check(!name.isConstructor) { "$name: constructors cannot have context receivers" } contextReceiverTypes += receiverTypes } From 7f61d7ab545122a1691f805976aafa988a12b7a5 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 02:16:52 +0200 Subject: [PATCH 09/21] Apply requested changes - Add more tests - Add new-line after context() --- .../kotlinpoet/ExperimentalKotlinPoetApi.kt | 15 +++++ .../java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- .../com/squareup/kotlinpoet/FunSpecTest.kt | 61 ++++++++++++++++++- .../squareup/kotlinpoet/LambdaTypeNameTest.kt | 45 +++++++++++++- 4 files changed, 119 insertions(+), 4 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt index 7a4377c9c1..dd7f4dde04 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt @@ -1,3 +1,18 @@ +/* + * 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 diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 9415639a44..a66be3463d 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -90,7 +90,7 @@ public class FunSpec private constructor( codeWriter.emitAnnotations(annotations, false) if (contextReceiverTypes.isNotEmpty()) { val receivers = contextReceiverTypes.joinToString(", ") { "%T" } - codeWriter.emitCode("context($receivers)·", *contextReceiverTypes.toTypedArray()) + codeWriter.emitCode("context($receivers)\n", *contextReceiverTypes.toTypedArray()) } codeWriter.emitModifiers(modifiers, implicitModifiers) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index b3871890c1..864663d3f5 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -33,6 +33,7 @@ import javax.lang.model.util.Types import kotlin.test.BeforeTest import kotlin.test.Test +@OptIn(ExperimentalKotlinPoetApi::class) class FunSpecTest { @Rule @JvmField val compilation = CompilationRule() @@ -69,6 +70,8 @@ class FunSpecTest { internal interface ExtendsOthers : Callable, Comparable + annotation class TestAnnotation + abstract class InvalidOverrideMethods { fun finalMethod() { } @@ -437,12 +440,68 @@ class FunSpecTest { assertThat(funSpec.toString()).isEqualTo( """ - |context(kotlin.String) public fun foo(): kotlin.Unit { + |context(kotlin.String) + |public fun foo(): kotlin.Unit { + |} + |""".trimMargin() + ) + } + + @Test fun functionWithMultipleContextReceiver() { + val stringType = STRING + val intType = INT + val booleanType = BOOLEAN + val funSpec = FunSpec.builder("foo") + .contextReceiver(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) + .contextReceiver(genericType) + .build() + + assertThat(funSpec.toString()).isEqualTo( + """ + |context(T) + |public fun foo(): kotlin.Unit { + |} + |""".trimMargin() + ) + } + + @Test fun functionWithAnnotatedContextReceiver() { + val genericType = STRING.copy(annotations = listOf(AnnotationSpec.get(TestAnnotation()))) + val funSpec = FunSpec.builder("foo") + .contextReceiver(genericType) + .build() + + assertThat(funSpec.toString()).isEqualTo( + """ + |context(@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation kotlin.String) + |public fun foo(): kotlin.Unit { + |} + |""".trimMargin() + ) + } + + @Test fun constructorWithContextReceiver() { + assertThrows { + FunSpec.constructorBuilder() + .contextReceiver(STRING) + }.hasMessageThat().isEqualTo("constructor() cannot have receiver type") + } + @Test fun functionParamSingleLambdaParam() { val unitType = UNIT val booleanType = BOOLEAN diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt index 89f9c7b069..1119c7239e 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt @@ -20,6 +20,7 @@ import com.squareup.kotlinpoet.KModifier.VARARG import javax.annotation.Nullable import kotlin.test.Test +@OptIn(ExperimentalKotlinPoetApi::class) class LambdaTypeNameTest { @Retention(AnnotationRetention.RUNTIME) @@ -54,15 +55,55 @@ class LambdaTypeNameTest { @Test fun contextReceiver() { val typeName = LambdaTypeName.get( Int::class.asTypeName(), - listOf(STRING), listOf(), - Unit::class.asTypeName() + Unit::class.asTypeName(), + listOf(STRING) ) assertThat(typeName.toString()).isEqualTo( "context(kotlin.String) kotlin.Int.() -> kotlin.Unit" ) } + @Test fun functionWithMultipleContextReceiver() { + 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 { LambdaTypeName.get( From 4d5792e69e503395aed26f0cd66037f419ece092 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 13 Apr 2022 02:18:23 +0200 Subject: [PATCH 10/21] Update test for suggestion --- kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index 864663d3f5..17ddf6577c 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -499,7 +499,7 @@ class FunSpecTest { assertThrows { FunSpec.constructorBuilder() .contextReceiver(STRING) - }.hasMessageThat().isEqualTo("constructor() cannot have receiver type") + }.hasMessageThat().isEqualTo("constructor(): constructors cannot have context receivers") } @Test fun functionParamSingleLambdaParam() { From f2ace6ff504af9b3016163614f5f097dacf4da23 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Fri, 15 Apr 2022 03:20:32 +0200 Subject: [PATCH 11/21] Apply suggestions from code review Co-authored-by: Egor Andreevich --- .../com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt | 2 +- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 6 ++++-- .../src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt | 2 +- .../test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt index dd7f4dde04..7182d604f7 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt @@ -21,7 +21,7 @@ import kotlin.annotation.AnnotationTarget.PROPERTY import kotlin.annotation.AnnotationTarget.TYPEALIAS /** - * Indicates that a given API is part of an experimental KotlinPoet and is + * Indicates that a given API is an experimental part of KotlinPoet and is * subject to API changes. */ @RequiresOptIn diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index bc694cfe0b..e3b7d4b4ec 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -89,8 +89,10 @@ public class FunSpec private constructor( } codeWriter.emitAnnotations(annotations, false) if (contextReceiverTypes.isNotEmpty()) { - val receivers = contextReceiverTypes.joinToString(", ") { "%T" } - codeWriter.emitCode("context($receivers)\n", *contextReceiverTypes.toTypedArray()) + val receivers = contextReceiverTypes + .map { CodeBlock.of("%T", it) } + .joinToCode(prefix = "context(", suffix = ")\n")) + codeWriter.emitCode(receivers) } codeWriter.emitModifiers(modifiers, implicitModifiers) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index 17ddf6577c..e1a6ec246d 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -447,7 +447,7 @@ class FunSpecTest { ) } - @Test fun functionWithMultipleContextReceiver() { + @Test fun functionWithMultipleContextReceivers() { val stringType = STRING val intType = INT val booleanType = BOOLEAN diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt index 1119c7239e..c119583c4e 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt @@ -64,7 +64,7 @@ class LambdaTypeNameTest { ) } - @Test fun functionWithMultipleContextReceiver() { + @Test fun functionWithMultipleContextReceivers() { val typeName = LambdaTypeName.get( Int::class.asTypeName(), listOf(), From e6316a0c99d60eb6f87c792846cd94817aeaedb7 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Fri, 15 Apr 2022 03:33:03 +0200 Subject: [PATCH 12/21] Apply requested changes --- .../com/squareup/kotlinpoet/CodeWriter.kt | 12 ++++++++++ .../java/com/squareup/kotlinpoet/FunSpec.kt | 8 ++----- .../com/squareup/kotlinpoet/LambdaTypeName.kt | 6 ++--- .../squareup/kotlinpoet/LambdaTypeNameTest.kt | 22 +++++++++---------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt index c208ad2e88..f401fb32bb 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt @@ -167,6 +167,18 @@ internal class CodeWriter constructor( } } + /** + * Emits the `context` block for [contextReceivers]. + */ + fun emitContextReceivers(contextReceivers: List) { + if (contextReceivers.isNotEmpty()) { + val receivers = contextReceivers + .map { CodeBlock.of("%T", it) } + .joinToCode(prefix = "context(", suffix = ")") + emitCode(receivers) + } + } + /** * 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. diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index e3b7d4b4ec..1020f858f4 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -88,12 +88,8 @@ public class FunSpec private constructor( codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine()) } codeWriter.emitAnnotations(annotations, false) - if (contextReceiverTypes.isNotEmpty()) { - val receivers = contextReceiverTypes - .map { CodeBlock.of("%T", it) } - .joinToCode(prefix = "context(", suffix = ")\n")) - codeWriter.emitCode(receivers) - } + codeWriter.emitContextReceivers(contextReceiverTypes) + codeWriter.emit("\n") codeWriter.emitModifiers(modifiers, implicitModifiers) if (!isConstructor && !name.isAccessor) { diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index cd7080ff03..85700b2bc7 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -57,10 +57,8 @@ public class LambdaTypeName private constructor( } override fun emit(out: CodeWriter): CodeWriter { - if (contextReceivers.isNotEmpty()) { - val receivers = contextReceivers.joinToString(", ") { "%T" } - out.emitCode("context($receivers)·", *contextReceivers.toTypedArray()) - } + out.emitContextReceivers(contextReceivers) + out.emit(" ") if (isNullable) { out.emit("(") } diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt index c119583c4e..28b16b4b72 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt @@ -31,9 +31,9 @@ class LambdaTypeNameTest { @Test fun receiverWithoutAnnotationHasNoParens() { val typeName = LambdaTypeName.get( - Int::class.asClassName(), - listOf(), - Unit::class.asTypeName() + receiver = Int::class.asClassName(), + parameters = listOf(), + returnType = Unit::class.asTypeName() ) assertThat(typeName.toString()).isEqualTo("kotlin.Int.() -> kotlin.Unit") } @@ -41,11 +41,11 @@ class LambdaTypeNameTest { @Test fun receiverWithAnnotationHasParens() { val annotation = IsAnnotated::class.java.getAnnotation(HasSomeAnnotation::class.java) val typeName = LambdaTypeName.get( - Int::class.asClassName().copy( - annotations = listOf(AnnotationSpec.get(annotation, includeDefaultValues = true)) + receiver = Int::class.asClassName().copy( + annotations = listOf(AnnotationSpec.get(annotation, includeDefaultValues = true)) ), - listOf(), - Unit::class.asTypeName() + parameters = listOf(), + returnType = Unit::class.asTypeName() ) assertThat(typeName.toString()).isEqualTo( "(@com.squareup.kotlinpoet.LambdaTypeNameTest.HasSomeAnnotation kotlin.Int).() -> kotlin.Unit" @@ -54,10 +54,10 @@ class LambdaTypeNameTest { @Test fun contextReceiver() { val typeName = LambdaTypeName.get( - Int::class.asTypeName(), - listOf(), - Unit::class.asTypeName(), - listOf(STRING) + receiver = Int::class.asTypeName(), + parameters = listOf(), + returnType = Unit::class.asTypeName(), + contextReceivers = listOf(STRING) ) assertThat(typeName.toString()).isEqualTo( "context(kotlin.String) kotlin.Int.() -> kotlin.Unit" From 1b1a5d9cd26573e3e7d6f71407d7d5e1a24dfe6d Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sat, 16 Apr 2022 01:26:54 +0200 Subject: [PATCH 13/21] Rename contextReceiver to contextReceivers --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 1020f858f4..69543a2a57 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -367,13 +367,13 @@ public class FunSpec private constructor( } @ExperimentalKotlinPoetApi - public fun contextReceiver(receiverTypes: Collection): Builder = apply { + public fun contextReceivers(receiverTypes: Collection): Builder = apply { check(!name.isConstructor) { "$name: constructors cannot have context receivers" } contextReceiverTypes += receiverTypes } @ExperimentalKotlinPoetApi - public fun contextReceiver(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) + public fun contextReceivers(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) @JvmOverloads public fun receiver( receiverType: TypeName, From ec31e4edf608b12f0a4747886da55654ec29d77d Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sat, 16 Apr 2022 20:06:55 +0200 Subject: [PATCH 14/21] Fix compiler error --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- 1 file changed, 1 insertion(+), 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 69543a2a57..fa4d3fefce 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -373,7 +373,7 @@ public class FunSpec private constructor( } @ExperimentalKotlinPoetApi - public fun contextReceivers(vararg receiverType: TypeName): Builder = contextReceiver(receiverType.toList()) + public fun contextReceivers(vararg receiverType: TypeName): Builder = contextReceivers(receiverType.toList()) @JvmOverloads public fun receiver( receiverType: TypeName, From 53d0af2bf614f8e0dc0591aca428ea13c8a6afae Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 17:13:20 +0200 Subject: [PATCH 15/21] Update kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt Co-authored-by: Egor Andreevich --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- 1 file changed, 1 insertion(+), 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 fa4d3fefce..34afaa96c8 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -367,7 +367,7 @@ public class FunSpec private constructor( } @ExperimentalKotlinPoetApi - public fun contextReceivers(receiverTypes: Collection): Builder = apply { + public fun contextReceivers(receiverTypes: Iterable): Builder = apply { check(!name.isConstructor) { "$name: constructors cannot have context receivers" } contextReceiverTypes += receiverTypes } From 57f9941158f25822179af8ab18a68646beffa883 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 17:29:16 +0200 Subject: [PATCH 16/21] Fix compiler errors in tests --- .../test/java/com/squareup/kotlinpoet/FunSpecTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index e1a6ec246d..1666fdea15 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -435,7 +435,7 @@ class FunSpecTest { @Test fun functionWithContextReceiver() { val stringType = STRING val funSpec = FunSpec.builder("foo") - .contextReceiver(stringType) + .contextReceivers(stringType) .build() assertThat(funSpec.toString()).isEqualTo( @@ -452,7 +452,7 @@ class FunSpecTest { val intType = INT val booleanType = BOOLEAN val funSpec = FunSpec.builder("foo") - .contextReceiver(stringType, intType, booleanType) + .contextReceivers(stringType, intType, booleanType) .build() assertThat(funSpec.toString()).isEqualTo( @@ -468,7 +468,7 @@ class FunSpecTest { val genericType = TypeVariableName("T") val funSpec = FunSpec.builder("foo") .addTypeVariable(genericType) - .contextReceiver(genericType) + .contextReceivers(genericType) .build() assertThat(funSpec.toString()).isEqualTo( @@ -483,7 +483,7 @@ class FunSpecTest { @Test fun functionWithAnnotatedContextReceiver() { val genericType = STRING.copy(annotations = listOf(AnnotationSpec.get(TestAnnotation()))) val funSpec = FunSpec.builder("foo") - .contextReceiver(genericType) + .contextReceivers(genericType) .build() assertThat(funSpec.toString()).isEqualTo( @@ -498,7 +498,7 @@ class FunSpecTest { @Test fun constructorWithContextReceiver() { assertThrows { FunSpec.constructorBuilder() - .contextReceiver(STRING) + .contextReceivers(STRING) }.hasMessageThat().isEqualTo("constructor(): constructors cannot have context receivers") } From cabea6bff0683fefce00c93e9869651b85286c90 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 17:53:13 +0200 Subject: [PATCH 17/21] Run spotless --- .../src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt index 28b16b4b72..dd44d71910 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt @@ -42,7 +42,7 @@ class LambdaTypeNameTest { 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)) + annotations = listOf(AnnotationSpec.get(annotation, includeDefaultValues = true)) ), parameters = listOf(), returnType = Unit::class.asTypeName() From ad2ec58eda0a534de7b707c9fae0027018b525ee Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 17:55:15 +0200 Subject: [PATCH 18/21] Update kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt Co-authored-by: Zac Sweers --- .../src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 85700b2bc7..dc13ca4c5c 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -58,7 +58,7 @@ public class LambdaTypeName private constructor( override fun emit(out: CodeWriter): CodeWriter { out.emitContextReceivers(contextReceivers) - out.emit(" ") + out.emit("·") if (isNullable) { out.emit("(") } From 92d9956b24ab6bc79d9aea595c1916e01fd63d06 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 17:59:38 +0200 Subject: [PATCH 19/21] Apply requested changes --- .../java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt | 3 +-- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- .../src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt index 7182d604f7..2e58556da5 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt @@ -21,8 +21,7 @@ import kotlin.annotation.AnnotationTarget.PROPERTY import kotlin.annotation.AnnotationTarget.TYPEALIAS /** - * Indicates that a given API is an experimental part of KotlinPoet and is - * subject to API changes. + * Indicates that a given API is experimental and subject to change. */ @RequiresOptIn @Retention(AnnotationRetention.BINARY) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 34afaa96c8..8a9022c693 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -368,7 +368,7 @@ public class FunSpec private constructor( @ExperimentalKotlinPoetApi public fun contextReceivers(receiverTypes: Iterable): Builder = apply { - check(!name.isConstructor) { "$name: constructors cannot have context receivers" } + check(!name.isConstructor) { "constructors cannot have context receivers" } contextReceiverTypes += receiverTypes } diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt index 1666fdea15..8804e7b3a2 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt @@ -499,7 +499,7 @@ class FunSpecTest { assertThrows { FunSpec.constructorBuilder() .contextReceivers(STRING) - }.hasMessageThat().isEqualTo("constructor(): constructors cannot have context receivers") + }.hasMessageThat().isEqualTo("constructors cannot have context receivers") } @Test fun functionParamSingleLambdaParam() { From ef4909de7e27a01c91b18f4226aae4aca21ba1c2 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 18:41:57 +0200 Subject: [PATCH 20/21] Don't emit context receiver spacing if there are no context receivers --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt | 3 ++- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 3 +-- .../src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt index f401fb32bb..9b68d949bf 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt @@ -170,12 +170,13 @@ internal class CodeWriter constructor( /** * Emits the `context` block for [contextReceivers]. */ - fun emitContextReceivers(contextReceivers: List) { + fun emitContextReceivers(contextReceivers: List, suffix: String = "") { if (contextReceivers.isNotEmpty()) { val receivers = contextReceivers .map { CodeBlock.of("%T", it) } .joinToCode(prefix = "context(", suffix = ")") emitCode(receivers) + emit(suffix) } } diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 8a9022c693..3dce277364 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -88,8 +88,7 @@ public class FunSpec private constructor( codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine()) } codeWriter.emitAnnotations(annotations, false) - codeWriter.emitContextReceivers(contextReceiverTypes) - codeWriter.emit("\n") + codeWriter.emitContextReceivers(contextReceiverTypes, "\n") codeWriter.emitModifiers(modifiers, implicitModifiers) if (!isConstructor && !name.isAccessor) { diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index dc13ca4c5c..7618aaee54 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -57,8 +57,7 @@ public class LambdaTypeName private constructor( } override fun emit(out: CodeWriter): CodeWriter { - out.emitContextReceivers(contextReceivers) - out.emit("·") + out.emitContextReceivers(contextReceivers, "·") if (isNullable) { out.emit("(") } From c97af64880b72445583bd0c35b4f43ecafa7d65e Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 18 Apr 2022 18:56:03 +0200 Subject: [PATCH 21/21] Apply suggestions from code review Co-authored-by: Egor Andreevich --- kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt | 2 +- .../src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt index 3dce277364..4d5f2428e7 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt @@ -88,7 +88,7 @@ public class FunSpec private constructor( codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine()) } codeWriter.emitAnnotations(annotations, false) - codeWriter.emitContextReceivers(contextReceiverTypes, "\n") + codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n") codeWriter.emitModifiers(modifiers, implicitModifiers) if (!isConstructor && !name.isAccessor) { diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt index 7618aaee54..2baa8d24c3 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -57,7 +57,7 @@ public class LambdaTypeName private constructor( } override fun emit(out: CodeWriter): CodeWriter { - out.emitContextReceivers(contextReceivers, "·") + out.emitContextReceivers(contextReceivers, suffix = "·") if (isNullable) { out.emit("(") }