diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt index 2c143d8009..be978b0f3c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWords.kt @@ -78,7 +78,8 @@ class RustReservedWordSymbolProvider( return base.toSymbol(shape) } val previousName = base.toMemberName(shape) - val escapedName = this.toMemberName(shape) + // Prefix leading digit with an underscore to avoid invalid identifiers; allow extra leading underscores. + val escapedName = this.toMemberName(shape).replace(Regex("^(_*\\d)"), "_$1") // if the names don't match and it isn't a simple escaping with `r#`, record a rename renamedSymbol.toBuilder().name(escapedName) .letIf(escapedName != previousName && !escapedName.contains("r#")) { diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt index 02b0ba14d7..32cfee5697 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt @@ -102,7 +102,8 @@ class EnumMemberModel( parentShape: Shape, definition: EnumDefinition, ): MaybeRenamed? { - val name = definition.name.orNull()?.toPascalCase() ?: return null + // Prefix leading digit with an underscore to avoid invalid identifiers; allow extra leading underscores. + val name = definition.name.orNull()?.toPascalCase()?.replace(Regex("^(_*\\d)"), "_$1") ?: return null // Create a fake member shape for symbol look up until we refactor to use EnumShape val fakeMemberShape = MemberShape.builder().id(parentShape.id.withMember(name)).target("smithy.api#String").build() diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWordsTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWordsTest.kt index 99b0217eb9..b03ad0e38c 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWordsTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustReservedWordsTest.kt @@ -206,5 +206,7 @@ internal class RustReservedWordSymbolProviderTest { expectEnumRename("SelfValue", MaybeRenamed("SelfValue_", "SelfValue")) expectEnumRename("SelfOther", MaybeRenamed("SelfOther", null)) expectEnumRename("SELF", MaybeRenamed("SelfValue", "Self")) + + expectEnumRename("_2DBarcode", MaybeRenamed("_2DBarcode", "2DBarcode")) } } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGeneratorTest.kt index 0528c2e364..01cd2e841b 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGeneratorTest.kt @@ -50,6 +50,9 @@ class EnumGeneratorTest { { value: "some-value-2", name: "someName2", documentation: "More documentation #escape" }, + { value: "2D_BARCODE", + name: "_2D_BARCODE", + documentation: "Example with leading digit." }, { value: "unknown", name: "unknown", documentation: "It has some docs that #need to be escaped" } @@ -80,6 +83,14 @@ class EnumGeneratorTest { } } + @Test + fun `it prefixes enum names with underscore to avoid generating invalid identifiers starting with a digit`() { + model("_2D_BARCODE").also { member -> + member.derivedName() shouldBe "_2DBarcode" + member.name()!!.renamedFrom shouldBe "2DBarcode" + } + } + @Test fun `it should render documentation`() { val rendered = RustWriter.forModule("model").also { model("some_name_1").render(it) }.toString()