Skip to content

Commit 018cd3e

Browse files
authored
fix: properly check if a member can be nullable (#702)
1 parent f14cea5 commit 018cd3e

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "69672ec4-a822-465c-80ac-2656dd3ff334",
3+
"type": "bugfix",
4+
"description": "Properly check if a member can be nullable"
5+
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jsoupVersion=1.14.3
2424
okHttpVersion=5.0.0-alpha.10
2525

2626
# codegen
27-
smithyVersion=1.23.0
27+
smithyVersion=1.25.0
2828
smithyGradleVersion=0.6.0
2929

3030
# testing/utility

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinSymbolProvider.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import software.amazon.smithy.kotlin.codegen.KotlinSettings
99
import software.amazon.smithy.kotlin.codegen.lang.kotlinReservedWords
1010
import software.amazon.smithy.kotlin.codegen.model.*
1111
import software.amazon.smithy.model.Model
12+
import software.amazon.smithy.model.knowledge.NullableIndex
1213
import software.amazon.smithy.model.shapes.*
1314
import software.amazon.smithy.model.traits.SparseTrait
1415
import software.amazon.smithy.model.traits.StreamingTrait
@@ -26,6 +27,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
2627
private val service = model.expectShape<ServiceShape>(settings.service)
2728
private val logger = Logger.getLogger(javaClass.name)
2829
private val escaper: ReservedWordSymbolProvider.Escaper
30+
private val nullableIndex = NullableIndex(model)
2931

3032
// model depth; some shapes use `toSymbol()` internally as they convert (e.g.) member shapes to symbols, this tracks
3133
// how deep in the model we have recursed
@@ -168,8 +170,11 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
168170
val targetShape =
169171
model.getShape(shape.target).orElseThrow { CodegenException("Shape not found: ${shape.target}") }
170172

171-
val targetSymbol = toSymbol(targetShape)
172-
173+
val targetSymbol = if (nullableIndex.isMemberNullable(shape, NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1)) {
174+
toSymbol(targetShape).toBuilder().boxed().build()
175+
} else {
176+
toSymbol(targetShape)
177+
}
173178
// figure out if we are referencing an event stream or not.
174179
// NOTE: unlike blob streams we actually re-use the target (union) shape which is why we can't do this
175180
// when visiting a unionShape() like we can for blobShape()
@@ -249,9 +254,7 @@ class KotlinSymbolProvider(private val model: Model, private val settings: Kotli
249254
val builder = Symbol.builder()
250255
.putProperty(SymbolProperty.SHAPE_KEY, shape)
251256
.name(typeName)
252-
253-
val explicitlyBoxed = shape?.hasTrait<@Suppress("DEPRECATION") software.amazon.smithy.model.traits.BoxTrait>() ?: false
254-
if (explicitlyBoxed || boxed) {
257+
if (boxed) {
255258
builder.boxed()
256259
}
257260
return builder

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/StructureGenerator.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ import software.amazon.smithy.codegen.core.Symbol
99
import software.amazon.smithy.kotlin.codegen.core.*
1010
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1111
import software.amazon.smithy.kotlin.codegen.model.*
12-
import software.amazon.smithy.model.shapes.BlobShape
13-
import software.amazon.smithy.model.shapes.MemberShape
14-
import software.amazon.smithy.model.shapes.ShapeType
15-
import software.amazon.smithy.model.shapes.StructureShape
12+
import software.amazon.smithy.model.knowledge.NullableIndex
13+
import software.amazon.smithy.model.shapes.*
1614
import software.amazon.smithy.model.traits.ErrorTrait
1715
import software.amazon.smithy.model.traits.HttpLabelTrait
1816
import software.amazon.smithy.model.traits.RetryableTrait
@@ -30,6 +28,7 @@ class StructureGenerator(
3028
private val symbolProvider = ctx.symbolProvider
3129
private val model = ctx.model
3230
private val symbol = ctx.symbolProvider.toSymbol(ctx.shape)
31+
private val nullableIndex = NullableIndex(ctx.model)
3332

3433
fun render() {
3534
writer.renderDocumentation(shape)
@@ -149,17 +148,15 @@ class StructureGenerator(
149148
// Return the appropriate hashCode fragment based on ShapeID of member target.
150149
private fun selectHashFunctionForShape(member: MemberShape): String {
151150
val targetShape = model.expectShape(member.target)
152-
// also available already in the byMember map
153-
val targetSymbol = symbolProvider.toSymbol(targetShape)
154-
151+
val isNullable = nullableIndex.isMemberNullable(member, NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT)
155152
return when (targetShape.type) {
156153
ShapeType.INTEGER ->
157-
when (targetSymbol.isBoxed) {
154+
when (isNullable) {
158155
true -> " ?: 0"
159156
else -> ""
160157
}
161158
ShapeType.BYTE ->
162-
when (targetSymbol.isBoxed) {
159+
when (isNullable) {
163160
true -> "?.toInt() ?: 0"
164161
else -> ".toInt()"
165162
}
@@ -172,7 +169,7 @@ class StructureGenerator(
172169
"?.contentHashCode() ?: 0"
173170
}
174171
else ->
175-
when (targetSymbol.isBoxed) {
172+
when (isNullable) {
176173
true -> "?.hashCode() ?: 0"
177174
else -> ".hashCode()"
178175
}

smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/core/SymbolProviderTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import software.amazon.smithy.kotlin.codegen.test.*
1919
import software.amazon.smithy.model.shapes.*
2020
import kotlin.test.Test
2121
import kotlin.test.assertEquals
22+
import kotlin.test.assertTrue
2223

2324
class SymbolProviderTest {
2425
@Test
@@ -97,6 +98,42 @@ class SymbolProviderTest {
9798
else -> typeName
9899
}
99100

101+
@Test
102+
fun `can read box trait from member`() {
103+
val model = """
104+
structure MyStruct {
105+
@box
106+
foo: MyFoo
107+
}
108+
long MyFoo
109+
""".prependNamespaceAndService().toSmithyModel()
110+
111+
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model)
112+
val member = model.expectShape<MemberShape>("com.test#MyStruct\$foo")
113+
val memberSymbol = provider.toSymbol(member)
114+
assertEquals("kotlin", memberSymbol.namespace)
115+
assertEquals("null", memberSymbol.defaultValue())
116+
assertTrue(memberSymbol.isBoxed)
117+
}
118+
119+
@Test
120+
fun `can read box trait from target`() {
121+
val model = """
122+
structure MyStruct {
123+
foo: MyFoo
124+
}
125+
@box
126+
long MyFoo
127+
""".prependNamespaceAndService().toSmithyModel()
128+
129+
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model)
130+
val member = model.expectShape<MemberShape>("com.test#MyStruct\$foo")
131+
val memberSymbol = provider.toSymbol(member)
132+
assertEquals("kotlin", memberSymbol.namespace)
133+
assertEquals("null", memberSymbol.defaultValue())
134+
assertTrue(memberSymbol.isBoxed)
135+
}
136+
100137
@Test
101138
fun `creates blobs`() {
102139
val model = """

0 commit comments

Comments
 (0)