From a47ada58c67c2adeb1847c4a948c46a96d742109 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 11 Jun 2021 14:52:58 -0400 Subject: [PATCH] Name collision refactor (#492) * Centralize colliding renames * Add integration test * Cleanup exception * Add tests & fixes for more invalid Rust identifiers --- .../model/naming-obstacle-course.smithy | 9 ++- .../codegen/rustlang/RustReservedWords.kt | 38 ++++++++++- .../codegen/smithy/SymbolMetadataProvider.kt | 5 ++ .../rust/codegen/smithy/SymbolVisitor.kt | 17 ++++- .../smithy/generators/EnumGenerator.kt | 57 +++++++---------- .../smithy/rust/codegen/testutil/Rust.kt | 6 ++ .../codegen/generators/EnumGeneratorTest.kt | 48 +++++++++++--- .../RustReservedWordSymbolProviderTest.kt | 63 +++++++++++++++++++ .../smithy/generators/BuilderGeneratorTest.kt | 6 ++ 9 files changed, 200 insertions(+), 49 deletions(-) create mode 100644 codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWordSymbolProviderTest.kt diff --git a/codegen-test/model/naming-obstacle-course.smithy b/codegen-test/model/naming-obstacle-course.smithy index 845180a2f2..d51ec3ec21 100644 --- a/codegen-test/model/naming-obstacle-course.smithy +++ b/codegen-test/model/naming-obstacle-course.smithy @@ -39,7 +39,11 @@ operation ReservedWordsAsMembers { structure ReservedWords { as: Integer, - async: Boolean + async: Boolean, + enum: UnknownVariantCollidingEnum, + self: Boolean, + crate: Boolean, + super: Boolean } @httpRequestTests([ @@ -97,5 +101,8 @@ structure CollidingException { @enum([ { name: "Known", value: "Known" }, { name: "Unknown", value: "Unknown" }, + { name: "Self", value: "Self" }, + { name: "UnknownValue", value: "UnknownValue" }, + { name: "SelfValue", value: "SelfValue" } ]) string UnknownVariantCollidingEnum diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt index d3cecb24d2..c2a63bd842 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt @@ -10,11 +10,16 @@ import software.amazon.smithy.codegen.core.ReservedWords import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.traits.EnumDefinition +import software.amazon.smithy.rust.codegen.smithy.MaybeRenamed import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.smithy.WrappingSymbolProvider +import software.amazon.smithy.rust.codegen.util.toPascalCase + +class RustReservedWordSymbolProvider(private val base: RustSymbolProvider) : WrappingSymbolProvider(base) { + private val internal = + ReservedWordSymbolProvider.builder().symbolProvider(base).memberReservedWords(RustReservedWords).build() -class RustReservedWordSymbolProvider(base: RustSymbolProvider) : WrappingSymbolProvider(base) { - private val internal = ReservedWordSymbolProvider.builder().symbolProvider(base).memberReservedWords(RustReservedWords).build() override fun toMemberName(shape: MemberShape): String { return internal.toMemberName(shape) } @@ -22,6 +27,28 @@ class RustReservedWordSymbolProvider(base: RustSymbolProvider) : WrappingSymbolP override fun toSymbol(shape: Shape): Symbol { return internal.toSymbol(shape) } + + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + val baseName = base.toEnumVariantName(definition) ?: return null + check(baseName.name.toPascalCase() == baseName.name) { + "Enum variants must already be in pascal case" + } + check(baseName.renamedFrom == null) { + "definitions should only pass through the renamer once" + } + return when (baseName.name) { + // Self cannot be used as a raw identifier, so we can't use the normal escaping strategy + // https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094/4 + "Self" -> MaybeRenamed("SelfValue", "Self") + // Real models won't end in `_` so it's safe to stop here + "SelfValue" -> MaybeRenamed("SelfValue_", "SelfValue") + // Unknown is used as the name of the variant containing unexpected values + "Unknown" -> MaybeRenamed("UnknownValue", "Unknown") + // Real models won't end in `_` so it's safe to stop here + "UnknownValue" -> MaybeRenamed("UnknownValue_", "UnknownValue") + else -> baseName + } + } } object RustReservedWords : ReservedWords { @@ -81,7 +108,12 @@ object RustReservedWords : ReservedWords { "try" ) - override fun escape(word: String): String = "r##$word" + private val cantBeRaw = setOf("self", "crate", "super") + + override fun escape(word: String): String = when { + cantBeRaw.contains(word) -> "${word}_" + else -> "r##$word" + } override fun isReserved(word: String): Boolean = RustKeywords.contains(word) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolMetadataProvider.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolMetadataProvider.kt index ac3dd966c1..c3a44272dc 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolMetadataProvider.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolMetadataProvider.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape +import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.rust.codegen.rustlang.Attribute import software.amazon.smithy.rust.codegen.rustlang.Attribute.Companion.NonExhaustive @@ -27,6 +28,10 @@ open class WrappingSymbolProvider(private val base: RustSymbolProvider) : RustSy return base.config() } + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + return base.toEnumVariantName(definition) + } + override fun toSymbol(shape: Shape): Symbol { return base.toSymbol(shape) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt index 74612d12a2..374e1a1630 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt @@ -34,6 +34,7 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape +import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.HttpLabelTrait @@ -44,6 +45,7 @@ import software.amazon.smithy.rust.codegen.smithy.traits.OutputBodyTrait import software.amazon.smithy.rust.codegen.smithy.traits.SyntheticInputTrait import software.amazon.smithy.rust.codegen.smithy.traits.SyntheticOutputTrait import software.amazon.smithy.rust.codegen.util.hasTrait +import software.amazon.smithy.rust.codegen.util.orNull import software.amazon.smithy.rust.codegen.util.toPascalCase import software.amazon.smithy.rust.codegen.util.toSnakeCase import software.amazon.smithy.utils.StringUtils @@ -71,7 +73,12 @@ data class SymbolVisitorConfig( // TODO: consider if this is better handled as a wrapper val DefaultConfig = - SymbolVisitorConfig(runtimeConfig = RuntimeConfig(), handleOptionality = true, handleRustBoxing = true, codegenConfig = CodegenConfig()) + SymbolVisitorConfig( + runtimeConfig = RuntimeConfig(), + handleOptionality = true, + handleRustBoxing = true, + codegenConfig = CodegenConfig() + ) data class SymbolLocation(val namespace: String) { val filename = "$namespace.rs" @@ -117,8 +124,11 @@ fun Symbol.Builder.locatedIn(symbolLocation: SymbolLocation): Symbol.Builder { .rustType(newRustType) } +data class MaybeRenamed(val name: String, val renamedFrom: String?) + interface RustSymbolProvider : SymbolProvider { fun config(): SymbolVisitorConfig + fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? } class SymbolVisitor( @@ -142,6 +152,11 @@ class SymbolVisitor( } } + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + val baseName = definition.name.orNull()?.toPascalCase() ?: return null + return MaybeRenamed(baseName, null) + } + override fun toMemberName(shape: MemberShape): String = shape.memberName.toSnakeCase() override fun blobShape(shape: BlobShape?): Symbol { diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/EnumGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/EnumGenerator.kt index acbfe425fe..b616c9e9c9 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/EnumGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/EnumGenerator.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.rust.codegen.smithy.generators -import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.traits.DocumentationTrait @@ -18,45 +17,38 @@ import software.amazon.smithy.rust.codegen.rustlang.rust import software.amazon.smithy.rust.codegen.rustlang.rustBlock import software.amazon.smithy.rust.codegen.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.rustlang.withBlock +import software.amazon.smithy.rust.codegen.smithy.MaybeRenamed import software.amazon.smithy.rust.codegen.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.util.doubleQuote import software.amazon.smithy.rust.codegen.util.getTrait import software.amazon.smithy.rust.codegen.util.orNull -import software.amazon.smithy.rust.codegen.util.toPascalCase /** Model that wraps [EnumDefinition] to calculate and cache values required to generate the Rust enum source. */ -internal class EnumMemberModel(private val definition: EnumDefinition) { +internal class EnumMemberModel(private val definition: EnumDefinition, private val symbolProvider: RustSymbolProvider) { // Because enum variants always start with an upper case letter, they will never // conflict with reserved words (which are always lower case), therefore, we never need // to fall back to raw identifiers - private val unescapedName: String? = definition.name.orNull()?.toPascalCase() - - val collidesWithUnknown: Boolean = unescapedName == EnumGenerator.UnknownVariant - - /** Enum name with correct case format and collision resolution */ - fun derivedName(): String = when (collidesWithUnknown) { - // If there is a variant named "Unknown", then rename it to "UnknownValue" so that it - // doesn't conflict with the code generator's "Unknown" variant that exists for backwards compatibility. - true -> "UnknownValue" - else -> checkNotNull(unescapedName) { "Enum variants must be named to derive a name. This is a bug." } - } val value: String get() = definition.value + fun name(): MaybeRenamed? = symbolProvider.toEnumVariantName(definition) + private fun renderDocumentation(writer: RustWriter) { + val name = + checkNotNull(name()) { "cannot generate docs for unnamed enum variants" } writer.docWithNote( definition.documentation.orNull(), - when (collidesWithUnknown) { - true -> - "`::${EnumGenerator.UnknownVariant}` has been renamed to `::${EnumGenerator.EscapedUnknownVariant}`. " + - "`::${EnumGenerator.UnknownVariant}` refers to additional values that may have been added since " + - "this enum was generated." - else -> null + name.renamedFrom?.let { renamedFrom -> + "`::$renamedFrom` has been renamed to `::${name.name}`." } + ) } + fun derivedName() = checkNotNull(symbolProvider.toEnumVariantName(definition)).name + fun render(writer: RustWriter) { renderDocumentation(writer) writer.write("${derivedName()},") @@ -74,7 +66,7 @@ private fun RustWriter.docWithNote(doc: String?, note: String?) { class EnumGenerator( private val model: Model, - symbolProvider: SymbolProvider, + private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StringShape, private val enumTrait: EnumTrait @@ -82,16 +74,13 @@ class EnumGenerator( private val symbol = symbolProvider.toSymbol(shape) private val enumName = symbol.name private val meta = symbol.expectRustMetadata() - private val sortedMembers: List = enumTrait.values.sortedBy { it.value }.map(::EnumMemberModel) + private val sortedMembers: List = + enumTrait.values.sortedBy { it.value }.map { EnumMemberModel(it, symbolProvider) } companion object { - /** - * For enums with named members, variants with names that collide with the generated unknown enum - * member get renamed to this [EscapedUnknownVariant] value. - */ - const val EscapedUnknownVariant = "UnknownValue" /** Name of the generated unknown enum member name for enums with named members. */ const val UnknownVariant = "Unknown" + /** Name of the function on the enum impl to get a vec of value names */ const val Values = "values" } @@ -142,20 +131,20 @@ class EnumGenerator( } private fun renderEnum() { + val renamedWarning = + sortedMembers.mapNotNull { it.name() }.filter { it.renamedFrom != null }.joinToString("\n") { + val previousName = it.renamedFrom!! + "`$enumName::$previousName` has been renamed to `::${it.name}`." + } writer.docWithNote( shape.getTrait()?.value, - when (sortedMembers.any { it.collidesWithUnknown }) { - true -> - "`$enumName::$UnknownVariant` has been renamed to `::$EscapedUnknownVariant`. " + - "`$enumName::$UnknownVariant` refers to additional values that may have been added since " + - "this enum was generated." - else -> null - } + renamedWarning.ifBlank { null } ) meta.render(writer) writer.rustBlock("enum $enumName") { sortedMembers.forEach { member -> member.render(writer) } + docs("$UnknownVariant contains new variants that have been added since this code was generated.") write("$UnknownVariant(String)") } } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/testutil/Rust.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/testutil/Rust.kt index 22645f7d95..34793a3354 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/testutil/Rust.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/testutil/Rust.kt @@ -14,6 +14,7 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.rust.codegen.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.rustlang.RustDependency import software.amazon.smithy.rust.codegen.rustlang.RustWriter @@ -21,6 +22,7 @@ import software.amazon.smithy.rust.codegen.rustlang.raw import software.amazon.smithy.rust.codegen.rustlang.rustBlock import software.amazon.smithy.rust.codegen.smithy.CodegenConfig import software.amazon.smithy.rust.codegen.smithy.DefaultPublicModules +import software.amazon.smithy.rust.codegen.smithy.MaybeRenamed import software.amazon.smithy.rust.codegen.smithy.RuntimeCrateLocation import software.amazon.smithy.rust.codegen.smithy.RustCrate import software.amazon.smithy.rust.codegen.smithy.RustSettings @@ -93,6 +95,10 @@ object TestWorkspace { TODO("Not yet implemented") } + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + TODO("Not yet implemented") + } + override fun toSymbol(shape: Shape?): Symbol { TODO("Not yet implemented") } diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/generators/EnumGeneratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/generators/EnumGeneratorTest.kt index 0468dbd598..bdc8f5af9c 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/generators/EnumGeneratorTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/generators/EnumGeneratorTest.kt @@ -9,7 +9,6 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.rust.codegen.rustlang.RustWriter @@ -40,22 +39,23 @@ class EnumGeneratorTest { ]) string EnumWithUnknown """.asSmithyModel() + private val symbolProvider = testSymbolProvider(testModel) private val enumTrait = testModel.lookup("test#EnumWithUnknown").expectTrait() private fun model(name: String): EnumMemberModel = - EnumMemberModel(enumTrait.values.first { it.name.orNull() == name }) + EnumMemberModel(enumTrait.values.first { it.name.orNull() == name }, symbolProvider) @Test fun `it converts enum names to PascalCase and renames any named Unknown to UnknownValue`() { model("some_name_1").derivedName() shouldBe "SomeName1" model("someName2").also { someName2 -> someName2.derivedName() shouldBe "SomeName2" - someName2.collidesWithUnknown shouldBe false + someName2.name()!!.renamedFrom shouldBe null } model("unknown").also { unknown -> unknown.derivedName() shouldBe "UnknownValue" - unknown.collidesWithUnknown shouldBe true + unknown.name()!!.renamedFrom shouldBe "Unknown" } } @@ -72,11 +72,12 @@ class EnumGeneratorTest { @Test fun `it adds a documentation note when renaming an enum named Unknown`() { val rendered = RustWriter.forModule("model").also { model("unknown").render(it) }.toString() + println(rendered.lines()) rendered shouldContain """ /// It has some docs /// - /// **NOTE:** `::Unknown` has been renamed to `::UnknownValue`. `::Unknown` refers to additional values that may have been added since this enum was generated. + /// **NOTE:** `::Unknown` has been renamed to `::UnknownValue`. UnknownValue, """.trimIndent() } @@ -104,7 +105,7 @@ class EnumGeneratorTest { ]) string InstanceType """.asSmithyModel() - val provider: SymbolProvider = testSymbolProvider(model) + val provider = testSymbolProvider(model) val writer = RustWriter.forModule("model") val shape = model.lookup("test#InstanceType") val generator = EnumGenerator(model, provider, writer, shape, shape.expectTrait()) @@ -206,7 +207,7 @@ class EnumGeneratorTest { """.asSmithyModel() val shape: StringShape = model.lookup("test#FooEnum") val trait = shape.expectTrait() - val provider: SymbolProvider = testSymbolProvider(model) + val provider = testSymbolProvider(model) val writer = RustWriter.forModule("model") val generator = EnumGenerator(model, provider, writer, shape, trait) generator.render() @@ -225,6 +226,7 @@ class EnumGeneratorTest { @enum([ { name: "Known", value: "Known" }, { name: "Unknown", value: "Unknown" }, + { name: "UnknownValue", value: "UnknownValue" }, ]) string SomeEnum """.asSmithyModel() @@ -238,6 +240,7 @@ class EnumGeneratorTest { writer.compileAndTest( """ assert_eq!(SomeEnum::from("Unknown"), SomeEnum::UnknownValue); + assert_eq!(SomeEnum::from("UnknownValue"), SomeEnum::UnknownValue_); assert_eq!(SomeEnum::from("SomethingNew"), SomeEnum::Unknown("SomethingNew".into())); """ ) @@ -247,7 +250,7 @@ class EnumGeneratorTest { fun `it should generate documentation for enums`() { val model = """ namespace test - + /// Some top-level documentation. @enum([ { name: "Known", value: "Known" }, @@ -265,7 +268,7 @@ class EnumGeneratorTest { """ /// Some top-level documentation. /// - /// **NOTE:** `SomeEnum::Unknown` has been renamed to `::UnknownValue`. `SomeEnum::Unknown` refers to additional values that may have been added since this enum was generated. + /// **NOTE:** `SomeEnum::Unknown` has been renamed to `::UnknownValue`. """.trimIndent() } @@ -273,7 +276,7 @@ class EnumGeneratorTest { fun `it should generate documentation for unnamed enums`() { val model = """ namespace test - + /// Some top-level documentation. @enum([ { value: "One" }, @@ -293,4 +296,29 @@ class EnumGeneratorTest { """.trimIndent() } } + + @Test + fun `it handles variants that clash with Rust reserved words`() { + val model = """ + namespace test + @enum([ + { name: "Known", value: "Known" }, + { name: "Self", value: "other" }, + ]) + string SomeEnum + """.asSmithyModel() + + val shape: StringShape = model.lookup("test#SomeEnum") + val trait = shape.expectTrait() + val provider = testSymbolProvider(model) + val writer = RustWriter.forModule("model") + EnumGenerator(model, provider, writer, shape, trait).render() + + writer.compileAndTest( + """ + assert_eq!(SomeEnum::from("other"), SomeEnum::SelfValue); + assert_eq!(SomeEnum::from("SomethingNew"), SomeEnum::Unknown("SomethingNew".into())); + """ + ) + } } diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWordSymbolProviderTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWordSymbolProviderTest.kt new file mode 100644 index 0000000000..5696470fbc --- /dev/null +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWordSymbolProviderTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package software.amazon.smithy.rust.codegen.rustlang + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.traits.EnumDefinition +import software.amazon.smithy.rust.codegen.smithy.MaybeRenamed +import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.smithy.SymbolVisitorConfig +import software.amazon.smithy.rust.codegen.util.orNull +import software.amazon.smithy.rust.codegen.util.toPascalCase + +internal class RustReservedWordSymbolProviderTest { + class Stub : RustSymbolProvider { + override fun config(): SymbolVisitorConfig { + TODO("Not yet implemented") + } + + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + return definition.name.orNull()?.let { MaybeRenamed(it.toPascalCase(), null) } + } + + override fun toSymbol(shape: Shape): Symbol { + return Symbol.builder().name(shape.id.name).build() + } + } + + @Test + fun `member names are escaped`() { + val provider = RustReservedWordSymbolProvider(Stub()) + provider.toMemberName( + MemberShape.builder().id("namespace#container\$async").target("namespace#Integer").build() + ) shouldBe "r##async" + + provider.toMemberName( + MemberShape.builder().id("namespace#container\$self").target("namespace#Integer").build() + ) shouldBe "self_" + } + + @Test + fun `enum variant names are updated to avoid conflicts`() { + expectEnumRename("Unknown", MaybeRenamed("UnknownValue", "Unknown")) + expectEnumRename("UnknownValue", MaybeRenamed("UnknownValue_", "UnknownValue")) + expectEnumRename("UnknownOther", MaybeRenamed("UnknownOther", null)) + + expectEnumRename("Self", MaybeRenamed("SelfValue", "Self")) + expectEnumRename("SelfValue", MaybeRenamed("SelfValue_", "SelfValue")) + expectEnumRename("SelfOther", MaybeRenamed("SelfOther", null)) + expectEnumRename("SELF", MaybeRenamed("SelfValue", "Self")) + } + + private fun expectEnumRename(original: String, expected: MaybeRenamed) { + val provider = RustReservedWordSymbolProvider(Stub()) + provider.toEnumVariantName(EnumDefinition.builder().name(original).value("foo").build()) shouldBe expected + } +} diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGeneratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGeneratorTest.kt index e1badb533f..cdc899f00f 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGeneratorTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGeneratorTest.kt @@ -9,9 +9,11 @@ import org.junit.jupiter.api.Test import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.rust.codegen.generators.StructureGeneratorTest import software.amazon.smithy.rust.codegen.rustlang.RustWriter import software.amazon.smithy.rust.codegen.smithy.Default +import software.amazon.smithy.rust.codegen.smithy.MaybeRenamed import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.smithy.SymbolVisitorConfig import software.amazon.smithy.rust.codegen.smithy.setDefault @@ -54,6 +56,10 @@ internal class BuilderGeneratorTest { return baseProvider.config() } + override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? { + return baseProvider.toEnumVariantName(definition) + } + override fun toSymbol(shape: Shape?): Symbol { return baseProvider.toSymbol(shape).toBuilder().setDefault(Default.NoDefault).build() }