Skip to content
9 changes: 9 additions & 0 deletions .changelog/fix-builder-accessor-naming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
applies_to: ["client", "server"]
authors: ["haydenbaker", "nated0g"]
references: ["smithy-rs#4338"]
breaking: false
new_feature: false
bug_fix: true
---
Fix builder accessor methods to use symbol provider for naming. This resolves conflicts when struct members are renamed due to reserved words (e.g., `meta` -> `meta_value`), ensuring setter and getter methods use the correct renamed field names.
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class SmokeTestsBuilderKindBehavior(val codegenContext: CodegenContext) : Instan
override fun hasFallibleBuilder(shape: StructureShape): Boolean =
BuilderGenerator.hasFallibleBuilder(shape, codegenContext.symbolProvider)

override fun setterName(memberShape: MemberShape): String = memberShape.setterName()
override fun setterName(memberShape: MemberShape): String = memberShape.setterName(codegenContext.symbolProvider)

override fun doesSetterTakeInOption(memberShape: MemberShape): Boolean = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test:
testOperationInput.operationParams.members.forEach { (key, value) ->
val member = operationInput.expectMember(key.value)
rustTemplate(
".${member.setterName()}(#{value})",
".${member.setterName(ctx.symbolProvider)}(#{value})",
"value" to instantiator.generate(member, value),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderInstantiator
import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName

class ClientBuilderInstantiator(private val clientCodegenContext: ClientCodegenContext) : BuilderInstantiator {
override fun setField(
Expand All @@ -25,6 +26,10 @@ class ClientBuilderInstantiator(private val clientCodegenContext: ClientCodegenC
return setFieldWithSetter(builder, value, field)
}

override fun setterProvider(field: MemberShape): String {
return field.setterName(clientCodegenContext.symbolProvider)
}

/**
* For the client, we finalize builders with error correction enabled
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ClientBuilderKindBehavior(val codegenContext: CodegenContext) : Instantiat
override fun hasFallibleBuilder(shape: StructureShape): Boolean =
BuilderGenerator.hasFallibleBuilder(shape, codegenContext.symbolProvider)

override fun setterName(memberShape: MemberShape): String = memberShape.setterName()
override fun setterName(memberShape: MemberShape): String = memberShape.setterName(codegenContext.symbolProvider)

override fun doesSetterTakeInOption(memberShape: MemberShape): Boolean = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ class FluentBuilderGenerator(
else -> renderInputHelper(member, memberName, coreType)
}
// pure setter
val setterName = member.setterName()
val setterName = member.setterName(symbolProvider)
val optionalInputType = outerType.asOptional()
renderInputHelper(member, setterName, optionalInputType)

val getterName = member.getterName()
val getterName = member.getterName(symbolProvider)
renderGetterHelper(member, getterName, optionalInputType)
}
}
Expand Down Expand Up @@ -437,7 +437,7 @@ class FluentBuilderGenerator(
"""
Appends an item to `${member.memberName}`.

To override the contents of this collection use [`${member.setterName()}`](Self::${member.setterName()}).
To override the contents of this collection use [`${member.setterName(symbolProvider)}`](Self::${member.setterName(symbolProvider)}).
""",
)
documentShape(member, model)
Expand Down Expand Up @@ -467,7 +467,7 @@ class FluentBuilderGenerator(
"""
Adds a key-value pair to `${member.memberName}`.

To override the contents of this collection use [`${member.setterName()}`](Self::${member.setterName()}).
To override the contents of this collection use [`${member.setterName(symbolProvider)}`](Self::${member.setterName(symbolProvider)}).
""",
)
documentShape(member, model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ private fun generateOperationShapeDocs(
val builderInputDoc = memberShape.asFluentBuilderInputDoc(symbolProvider)
val builderInputLink = docLink("$fluentBuilderFullyQualifiedName::${symbolProvider.toMemberName(memberShape)}")
val builderSetterDoc = memberShape.asFluentBuilderSetterDoc(symbolProvider)
val builderSetterLink = docLink("$fluentBuilderFullyQualifiedName::${memberShape.setterName()}")
val builderSetterLink = docLink("$fluentBuilderFullyQualifiedName::${memberShape.setterName(symbolProvider)}")

val docTrait = memberShape.getMemberTrait(model, DocumentationTrait::class.java).orNull()
val docs =
Expand Down Expand Up @@ -440,7 +440,7 @@ internal fun MemberShape.asFluentBuilderInputDoc(symbolProvider: SymbolProvider)
* _NOTE: This function generates the setter type names that appear under **"The fluent builder is configurable:"**_
*/
private fun MemberShape.asFluentBuilderSetterDoc(symbolProvider: SymbolProvider): String {
val memberName = this.setterName()
val memberName = this.setterName(symbolProvider)
val outerType = symbolProvider.toSymbol(this).rustType()

return "$memberName(${outerType.asArgumentType(fullyQualified = false)})"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class ProtocolParserGenerator(
val member = binding.member
val parsedValue = renderBindingParser(binding, operationShape, httpBindingGenerator, structuredDataParser)
if (parsedValue != null) {
withBlock("output = output.${member.setterName()}(", ");") {
withBlock("output = output.${member.setterName(symbolProvider)}(", ");") {
parsedValue(this)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,60 @@ class FluentClientGeneratorTest {

clientIntegrationTest(model)
}

@Test
fun `meta field gets renamed to meta_value with correct setter`() {
val model =
"""
namespace com.example
use aws.protocols#awsJson1_0

@awsJson1_0
service TestService {
operations: [TestOperation],
version: "1"
}

operation TestOperation { input: TestInput }
structure TestInput {
meta: String,
other: String
}
""".asSmithyModel()

clientIntegrationTest(model) { codegenContext, rustCrate ->
rustCrate.integrationTest("meta_field_renamed") {
val moduleName = codegenContext.moduleUseName()
rustTemplate(
"""
##[test]
fn test_meta_field_accessors() {
let config = $moduleName::Config::builder()
.endpoint_url("http://localhost:1234")
.http_client(#{NeverClient}::new())
.build();
let client = $moduleName::Client::from_conf(config);

// Test the renamed field and setter
let builder = client.test_operation()
.meta_value("test_meta")
.set_meta_value(Some("test_meta_2".to_string()))
.other("other_value");

// Verify getter returns correct value
assert_eq!(*builder.get_meta_value(), Some("test_meta_2".to_string()));
assert_eq!(*builder.get_other(), Some("other_value".to_string()));

// Build the input and verify field is accessible
let input = builder.as_input();
assert_eq!(*input.get_meta_value(), Some("test_meta_2".to_string()));
}
""",
"NeverClient" to
CargoDependency.smithyHttpClientTestUtil(codegenContext.runtimeConfig).toType()
.resolve("test_util::NeverClient"),
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,15 @@ class OperationBuildError(private val runtimeConfig: RuntimeConfig) {
}
}

// Setter names will never hit a reserved word and therefore never need escaping.
fun MemberShape.setterName() = "set_${this.memberName.toSnakeCase()}"
fun MemberShape.setterName(symbolProvider: SymbolProvider): String {
val unescaped = symbolProvider.toMemberName(this).removePrefix("r##")
return "set_$unescaped"
}

// Getter names will never hit a reserved word and therefore never need escaping.
fun MemberShape.getterName() = "get_${this.memberName.toSnakeCase()}"
fun MemberShape.getterName(symbolProvider: SymbolProvider): String {
val unescaped = symbolProvider.toMemberName(this).removePrefix("r##")
return "get_$unescaped"
}

class BuilderGenerator(
private val model: Model,
Expand Down Expand Up @@ -192,7 +196,7 @@ class BuilderGenerator(
val memberName = member.memberName.toSnakeCase()
val setter =
if (symbolProvider.toSymbol(member).isOptional()) {
member.setterName()
member.setterName(symbolProvider)
} else {
memberName
}
Expand Down Expand Up @@ -311,7 +315,7 @@ class BuilderGenerator(

writer.documentShape(member, model)
writer.deprecatedShape(member)
writer.rustBlock("pub fn ${member.setterName()}(mut self, input: ${inputType.render(true)}) -> Self") {
writer.rustBlock("pub fn ${member.setterName(symbolProvider)}(mut self, input: ${inputType.render(true)}) -> Self") {
rust("self.$memberName = input; self")
}
}
Expand All @@ -333,7 +337,7 @@ class BuilderGenerator(

writer.documentShape(member, model)
writer.deprecatedShape(member)
writer.rustBlock("pub fn ${member.getterName()}(&self) -> &${inputType.render(true)}") {
writer.rustBlock("pub fn ${member.getterName(symbolProvider)}(&self) -> &${inputType.render(true)}") {
rust("&self.$memberName")
}
}
Expand Down Expand Up @@ -409,7 +413,13 @@ class BuilderGenerator(
) {
docs("Appends an item to `$memberName`.")
rust("///")
docs("To override the contents of this collection use [`${member.setterName()}`](Self::${member.setterName()}).")
docs(
"To override the contents of this collection use [`${member.setterName(symbolProvider)}`](Self::${
member.setterName(
symbolProvider,
)
}).",
)
rust("///")
documentShape(member, model, autoSuppressMissingDocs = false)
deprecatedShape(member)
Expand All @@ -435,7 +445,13 @@ class BuilderGenerator(
) {
docs("Adds a key-value pair to `$memberName`.")
rust("///")
docs("To override the contents of this collection use [`${member.setterName()}`](Self::${member.setterName()}).")
docs(
"To override the contents of this collection use [`${member.setterName(symbolProvider)}`](Self::${
member.setterName(
symbolProvider,
)
}).",
)
rust("///")
documentShape(member, model, autoSuppressMissingDocs = false)
deprecatedShape(member)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ interface BuilderInstantiator {
mapErr: Writable? = null,
): Writable

fun setterProvider(field: MemberShape): String

/** Set a field on a builder using the `$setterName` method. $value will be passed directly. */
fun setFieldWithSetter(
builder: String,
value: Writable,
field: MemberShape,
) = writable {
rustTemplate("$builder = $builder.${field.setterName()}(#{value})", "value" to value)
rustTemplate("$builder = $builder.${setterProvider(field)}(#{value})", "value" to value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class CborParserGenerator(
rustBlock("${member.memberName.dq()} =>") {
val callBuilderSetMemberFieldWritable =
writable {
withBlock("builder.${member.setterName()}(", ")") {
withBlock("builder.${member.setterName(symbolProvider)}(", ")") {
conditionalBlock("Some(", ")", shouldWrapBuilderMemberSetterInputWithOption(member)) {
val symbol = symbolProvider.toSymbol(member)
if (symbol.isRustBoxed()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class EventStreamUnmarshallerGenerator(
}

private fun RustWriter.renderUnmarshallEventHeader(member: MemberShape) {
withBlock("builder = builder.${member.setterName()}(", ");") {
withBlock("builder = builder.${member.setterName(symbolProvider)}(", ");") {
conditionalBlock("Some(", ")", member.isOptional) {
when (val target = model.expectShape(member.target)) {
is BooleanShape -> rustTemplate("#{expect_fns}::expect_bool(header)?", *codegenScope)
Expand Down Expand Up @@ -287,7 +287,7 @@ class EventStreamUnmarshallerGenerator(
*codegenScope,
)
}
withBlock("builder = builder.${member.setterName()}(", ");") {
withBlock("builder = builder.${member.setterName(symbolProvider)}(", ");") {
conditionalBlock("Some(", ")", member.isOptional) {
when (target) {
is BlobShape -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,14 @@ class JsonParserGenerator(
rustBlock("${jsonName(member).dq()} =>") {
when (codegenTarget) {
CodegenTarget.CLIENT -> {
withBlock("builder = builder.${member.setterName()}(", ");") {
withBlock("builder = builder.${member.setterName(symbolProvider)}(", ");") {
deserializeMember(member)
}
}

CodegenTarget.SERVER -> {
if (symbolProvider.toSymbol(member).isOptional()) {
withBlock("builder = builder.${member.setterName()}(", ");") {
withBlock("builder = builder.${member.setterName(symbolProvider)}(", ");") {
deserializeMember(member)
}
} else {
Expand All @@ -278,7 +278,7 @@ class JsonParserGenerator(
rust(
"""
{
builder = builder.${member.setterName()}(v);
builder = builder.${member.setterName(symbolProvider)}(v);
}
""",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ class XmlBindingTraitParserGenerator(
Ctx(tag = decoder, accum = "$builder.${symbolProvider.toMemberName(member)}.take()"),
)
}
rust("$builder = $builder.${member.setterName()}($temp);")
rust("$builder = $builder.${member.setterName(symbolProvider)}($temp);")
}
rustTemplate(
"_ => return Err(#{XmlDecodeError}::custom(\"expected ${member.xmlName()} tag\"))",
Expand Down Expand Up @@ -333,7 +333,7 @@ class XmlBindingTraitParserGenerator(
forceOptional = true,
)
}
rust("$builder = $builder.${member.setterName()}($temp);")
rust("$builder = $builder.${member.setterName(symbolProvider)}($temp);")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderInstantiator
import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName

/**
* A Default instantiator that uses `builder.build()` in all cases. This exists to support tests in codegen-core
Expand All @@ -27,6 +28,10 @@ class DefaultBuilderInstantiator(private val checkFallibleBuilder: Boolean, priv
return setFieldWithSetter(builder, value, field)
}

override fun setterProvider(field: MemberShape): String {
return field.setterName(symbolProvider)
}

override fun finalizeBuilder(
builder: String,
shape: StructureShape,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class InstantiatorTest {
override fun hasFallibleBuilder(shape: StructureShape) =
BuilderGenerator.hasFallibleBuilder(shape, codegenContext.symbolProvider)

override fun setterName(memberShape: MemberShape) = memberShape.setterName()
override fun setterName(memberShape: MemberShape) = memberShape.setterName(codegenContext.symbolProvider)

override fun doesSetterTakeInOption(memberShape: MemberShape) = true
}
Expand Down
Loading
Loading