diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt index e4d27fe4516b35e949a66db97a44d825da71bec5..56f0b63c52b70aea8570bed08ef58cfd33df41a0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt @@ -385,11 +385,17 @@ data class RustMetadata( this.copy(derives = derives - withoutDerives.toSet()) fun renderAttributes(writer: RustWriter): RustMetadata { - additionalAttributes.forEach { + val (deriveHelperAttrs, otherAttrs) = additionalAttributes.partition { it.isDeriveHelper } + otherAttrs.forEach { it.render(writer) } + Attribute(derive(derives)).render(writer) + // Derive helper attributes must come after derive, see https://github.com/rust-lang/rust/issues/79202 + deriveHelperAttrs.forEach { + it.render(writer) + } return this } @@ -450,17 +456,22 @@ enum class AttributeKind { * [Attributes](https://doc.rust-lang.org/reference/attributes.html) are general free form metadata * that are interpreted by the compiler. * + * If the attribute is a "derive helper", such as `#[serde]`, set `isDeriveHelper` to `true` so it is sorted correctly after + * the derive attribute is rendered. (See https://github.com/rust-lang/rust/issues/79202 for why sorting matters.) + * * For example: * ```rust + * #[allow(missing_docs)] // <-- this is an attribute, and it is not a derive helper * #[derive(Clone, PartialEq, Serialize)] // <-- this is an attribute - * #[serde(serialize_with = "abc")] // <-- this is an attribute + * #[serde(serialize_with = "abc")] // <-- this attribute is a derive helper because the `Serialize` derive uses it * struct Abc { * a: i64 * } * ``` */ -class Attribute(val inner: Writable) { +class Attribute(val inner: Writable, val isDeriveHelper: Boolean = false) { constructor(str: String) : this(writable(str)) + constructor(str: String, isDeriveHelper: Boolean) : this(writable(str), isDeriveHelper) constructor(runtimeType: RuntimeType) : this(runtimeType.writable) fun render(writer: RustWriter, attributeKind: AttributeKind = AttributeKind.Outer) { diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt index 3b0ef7c557639b8191303c851a8ba4b40a597caf..ac14bcd20f37b8b123ab2b05c6d06d69dc2d4d05 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt @@ -133,6 +133,19 @@ class RustWriterTest { sut.toString().shouldContain("#[foo]\n/// here's an attribute") } + @Test + fun `attributes with derive helpers must come after derives`() { + val attr = Attribute("foo", isDeriveHelper = true) + val metadata = RustMetadata( + derives = setOf(RuntimeType.Debug), + additionalAttributes = listOf(Attribute.AllowDeprecated, attr), + visibility = Visibility.PUBLIC, + ) + val sut = RustWriter.root() + metadata.render(sut) + sut.toString().shouldContain("#[allow(deprecated)]\n#[derive(std::fmt::Debug)]\n#[foo]") + } + @Test fun `deprecated attribute without any field`() { val sut = RustWriter.root()