Skip to content

Commit

Permalink
Merge pull request #1575 from drawers/fix/1525
Browse files Browse the repository at this point in the history
Allow properties with context receivers in interfaces to omit declaring accessors
  • Loading branch information
Egorand committed May 29, 2023
2 parents cf2e019 + 1c353cf commit 2c3dbb8
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,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(
Expand Down
14 changes: 14 additions & 0 deletions kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
176 changes: 176 additions & 0 deletions kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
Expand Down

0 comments on commit 2c3dbb8

Please sign in to comment.