Skip to content

Commit 00aecac

Browse files
committed
Fix unused variable warnings in QuerySerializerGenerator for unions with empty structs
- Add isEmptyStruct() helper to detect empty struct shapes - Use _inner for empty structs to avoid unused variable warnings - Use inner for non-empty structs where variable is used - Add comprehensive tests for RestXml, AwsQuery, and RestJson protocols - Tests enforce -D warnings via clientIntegrationTest
1 parent 1bf6c6a commit 00aecac

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
lines changed

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryTest.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,51 @@ class AwsQueryTest {
4444
fun `generate an aws query service that compiles`() {
4545
clientIntegrationTest(model) { _, _ -> }
4646
}
47+
48+
@Test
49+
fun `union with empty struct generates warning-free code`() {
50+
val modelWithEmptyStruct =
51+
"""
52+
namespace test
53+
use aws.protocols#awsQuery
54+
55+
@awsQuery
56+
@xmlNamespace(uri: "https://example.com/")
57+
service TestService {
58+
version: "2019-12-16",
59+
operations: [TestOp]
60+
}
61+
62+
operation TestOp {
63+
input: TestInput,
64+
output: TestOutput
65+
}
66+
67+
structure TestInput {
68+
testUnion: TestUnion
69+
}
70+
71+
structure TestOutput {
72+
testUnion: TestUnion
73+
}
74+
75+
union TestUnion {
76+
// Empty struct - should generate _inner to avoid unused variable warning
77+
emptyStruct: EmptyStruct,
78+
79+
// Normal struct - should generate inner (without underscore)
80+
normalStruct: NormalStruct
81+
}
82+
83+
structure EmptyStruct {}
84+
85+
structure NormalStruct {
86+
value: String
87+
}
88+
""".asSmithyModel()
89+
90+
// This test will fail with unused variable warnings if the fix is not applied
91+
// clientIntegrationTest enforces -D warnings via codegenIntegrationTest
92+
clientIntegrationTest(modelWithEmptyStruct) { _, _ -> }
93+
}
4794
}

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/RestJsonTest.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,55 @@ internal class RestJsonTest {
4545
fun `generate a rest json service that compiles`() {
4646
clientIntegrationTest(model) { _, _ -> }
4747
}
48+
49+
@Test
50+
fun `union with empty struct always uses inner variable`() {
51+
val modelWithEmptyStruct =
52+
"""
53+
namespace test
54+
use aws.protocols#restJson1
55+
use aws.api#service
56+
57+
@service(sdkId: "Rest Json Empty Struct")
58+
@restJson1
59+
service RestJsonEmptyStruct {
60+
version: "2019-12-16",
61+
operations: [TestOp]
62+
}
63+
64+
@http(uri: "/test", method: "POST")
65+
operation TestOp {
66+
input: TestInput,
67+
output: TestOutput
68+
}
69+
70+
structure TestInput {
71+
testUnion: TestUnion
72+
}
73+
74+
structure TestOutput {
75+
testUnion: TestUnion
76+
}
77+
78+
union TestUnion {
79+
// Empty struct - RestJson ALWAYS uses inner variable, no warning
80+
emptyStruct: EmptyStruct,
81+
82+
// Normal struct - RestJson uses inner variable
83+
normalStruct: NormalStruct
84+
}
85+
86+
structure EmptyStruct {}
87+
88+
structure NormalStruct {
89+
value: String
90+
}
91+
""".asSmithyModel()
92+
93+
// This test documents that RestJson protocol is immune to unused variable issues.
94+
// Unlike RestXml/AwsQuery, RestJson serializers always reference the inner variable
95+
// even for empty structs, so no underscore prefix is needed.
96+
// This test passes without any code changes, proving RestJson immunity.
97+
clientIntegrationTest(modelWithEmptyStruct) { _, _ -> }
98+
}
4899
}

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/RestXmlTest.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,53 @@ internal class RestXmlTest {
4848
fun `generate a rest xml service that compiles`() {
4949
clientIntegrationTest(model) { _, _ -> }
5050
}
51+
52+
@Test
53+
fun `union with empty struct generates warning-free code`() {
54+
val modelWithEmptyStruct =
55+
"""
56+
namespace test
57+
use aws.protocols#restXml
58+
use aws.api#service
59+
60+
@service(sdkId: "Rest XML Empty Struct")
61+
@restXml
62+
service RestXmlEmptyStruct {
63+
version: "2019-12-16",
64+
operations: [TestOp]
65+
}
66+
67+
@http(uri: "/test", method: "POST")
68+
operation TestOp {
69+
input: TestInput,
70+
output: TestOutput
71+
}
72+
73+
structure TestInput {
74+
testUnion: TestUnion
75+
}
76+
77+
structure TestOutput {
78+
testUnion: TestUnion
79+
}
80+
81+
union TestUnion {
82+
// Empty struct - should generate _inner to avoid unused variable warning
83+
emptyStruct: EmptyStruct,
84+
85+
// Normal struct - should generate inner (without underscore)
86+
normalStruct: NormalStruct
87+
}
88+
89+
structure EmptyStruct {}
90+
91+
structure NormalStruct {
92+
value: String
93+
}
94+
""".asSmithyModel()
95+
96+
// This test will fail with unused variable warnings if the fix is not applied
97+
// clientIntegrationTest enforces -D warnings via codegenIntegrationTest
98+
clientIntegrationTest(modelWithEmptyStruct) { _, _ -> }
99+
}
51100
}

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,16 @@ abstract class QuerySerializerGenerator(private val codegenContext: CodegenConte
345345
}
346346
}
347347

348+
/**
349+
* Determines if a struct shape is empty (has no members).
350+
* Empty structs result in unused variables in union match arms since the inner value is never referenced.
351+
*/
352+
private fun isEmptyStruct(shape: Shape): Boolean =
353+
when (shape) {
354+
is StructureShape -> shape.members().isEmpty()
355+
else -> false
356+
}
357+
348358
private fun RustWriter.serializeUnion(context: Context<UnionShape>) {
349359
val unionSymbol = symbolProvider.toSymbol(context.shape)
350360
val unionSerializer =
@@ -357,17 +367,21 @@ abstract class QuerySerializerGenerator(private val codegenContext: CodegenConte
357367
) {
358368
rustBlock("match input") {
359369
for (member in context.shape.members()) {
370+
val targetShape = model.expectShape(member.target)
371+
// Use underscore prefix for empty structs to avoid unused variable warnings
372+
val innerVarName = if (isEmptyStruct(targetShape)) "_inner" else "inner"
373+
360374
val variantName =
361375
if (member.isTargetUnit()) {
362376
"${symbolProvider.toMemberName(member)}"
363377
} else {
364-
"${symbolProvider.toMemberName(member)}(inner)"
378+
"${symbolProvider.toMemberName(member)}($innerVarName)"
365379
}
366380
withBlock("#T::$variantName => {", "},", unionSymbol) {
367381
serializeMember(
368382
MemberContext.unionMember(
369383
context.copy(writerExpression = "writer"),
370-
"inner",
384+
innerVarName,
371385
member,
372386
),
373387
)

0 commit comments

Comments
 (0)