diff --git a/build.gradle b/build.gradle index 28e6d6f3dc..159075f112 100644 --- a/build.gradle +++ b/build.gradle @@ -683,6 +683,7 @@ tasks.register('generateRustTestCodecs', JavaExec) { 'sbe-tool/src/test/resources/issue1057.xml', 'sbe-tool/src/test/resources/issue1066.xml', 'sbe-tool/src/test/resources/issue1116.xml', + 'sbe-tool/src/test/resources/issue1118.xml', 'sbe-tool/src/test/resources/optional_enum_nullify.xml', 'sbe-tool/src/test/resources/basic-variable-length-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1853a174fc..cccd25c1bd 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -20,6 +20,7 @@ issue_1028 = { path = "../generated/rust/issue1028" } issue_1057 = { path = "../generated/rust/issue1057" } issue_1066 = { path = "../generated/rust/issue1066" } issue_1116 = { path = "../generated/rust/issue1116" } +issue_1118 = { path = "../generated/rust/issue1118" } baseline_bigendian = { path = "../generated/rust/baseline_bigendian" } nested_composite_name = { path = "../generated/rust/nested_composite_name" } sbe_tests = { path = "../generated/rust/sbe_tests" } diff --git a/rust/tests/issue_1118_test.rs b/rust/tests/issue_1118_test.rs new file mode 100644 index 0000000000..a146ce4b6a --- /dev/null +++ b/rust/tests/issue_1118_test.rs @@ -0,0 +1,64 @@ +use issue_1118::{ + flags::Flags, + issue_1118_codec::{self, Issue1118Decoder, Issue1118Encoder}, + message_header_codec, ReadBuf, WriteBuf, +}; + +/// Choices named after a Rust keyword are reached through `get_` and `set_` prefixed accessors, +/// so the prefixed name is a plain identifier and the choice name is not escaped. This module +/// failing to compile is itself the regression this guards, since `get_r#type` does not parse. +#[test] +fn keyword_named_choices_have_usable_accessors() { + let mut flags = Flags::new(0); + + flags.set_type(true); + assert!(flags.get_type()); + assert!(!flags.get_match()); + assert!(!flags.get_ordinary()); + + flags.set_match(true); + flags.set_type(false); + assert!(!flags.get_type()); + assert!(flags.get_match()); + + flags.clear(); + assert_eq!(flags.0, 0); +} + +/// The choice name is written into `Debug` as a label, so it should not carry the raw identifier +/// escaping either. +#[test] +fn debug_uses_the_unescaped_choice_names() { + let mut flags = Flags::new(0); + flags.set_type(true).set_ordinary(true); + + assert_eq!( + format!("{flags:?}"), + "Flags[type(0)=true,match(1)=false,ordinary(2)=true]" + ); +} + +#[test] +fn round_trips_a_keyword_named_choice() { + let mut buffer = vec![0u8; 256]; + + { + let mut encoder = Issue1118Encoder::default().wrap( + WriteBuf::new(&mut buffer), + message_header_codec::ENCODED_LENGTH, + ); + let mut flags = Flags::new(0); + flags.set_match(true); + encoder.flags(flags); + } + + let decoder = Issue1118Decoder::default().wrap( + ReadBuf::new(&buffer), + message_header_codec::ENCODED_LENGTH, + issue_1118_codec::SBE_BLOCK_LENGTH, + 0, + ); + let flags = decoder.flags(); + assert!(flags.get_match()); + assert!(!flags.get_type()); +} diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java index 2d6b310e62..e914870870 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java @@ -1441,7 +1441,7 @@ private static void generateSingleBitSet( continue; } - final String choiceName = formatFunctionName(token.name()); + final String choiceName = bitSetChoiceName(token); final Encoding encoding = token.encoding(); final String choiceBitIndex = encoding.constValue().toString(); @@ -1479,7 +1479,7 @@ private static void generateSingleBitSet( continue; } - final String choiceName = formatFunctionName(token.name()); + final String choiceName = bitSetChoiceName(token); final String choiceBitIndex = token.encoding().constValue().toString(); if (comma) @@ -1498,6 +1498,14 @@ private static void generateSingleBitSet( indent(writer, 0, "}\n"); } + // A choice is only ever reached through a get_ or set_ prefixed accessor, so its name cannot + // shadow a Rust keyword on its own and must not be escaped as a raw identifier. Escaping it + // would place the r# in the middle of the accessor name, where it does not parse. + private static String bitSetChoiceName(final Token token) + { + return toLowerSnakeCase(token.name()); + } + static void appendImplEncoderTrait( final Appendable out, final String typeName, diff --git a/sbe-tool/src/test/resources/issue1118.xml b/sbe-tool/src/test/resources/issue1118.xml new file mode 100644 index 0000000000..a77e8f5677 --- /dev/null +++ b/sbe-tool/src/test/resources/issue1118.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + 0 + 1 + 2 + + + + + +