Skip to content

Commit dcf16ac

Browse files
edisongustavoGustavo Muenz
andauthored
Remove attributes annotation of Builder struct (#3674)
The `struct` created for a Builder should not have attributes because these structs only derive from `Debug`, `PartialEq`, `Clone` and `Default`. None of these support attributes. This becomes a problem if a plugin tries to add attributes to the `metadata` field to configure the generated code for the `struct`. In this case, the attributes will also be added to the `Builder` struct; which is wrong. ## Motivation and Context I'm customizing client code generation by overriding the method: ```kotlin override fun symbolProvider(base: RustSymbolProvider): RustSymbolProvider ``` I override the `symbol.expectRustMetadata().additionalAttributes` value and add extra values there. The generated code adds the correct attributes for the `struct`, however, it also adds these attributes to the `Builder`. The `Builder` is restricted to deriving from `Debug`, `PartialEq`, `Clone` and `Default` (see [code](https://github.com/smithy-lang/smithy-rs/blob/a76dc184c2cf52d6027115f64fe78ab0c2a429e8/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt#L188)). I believe this change was added by mistake in #2222. ## Description To solve the issue, I simply remove the offending line. <!--- Describe your changes in detail --> ## Testing I don't expect any existing code to break, given that this is a bug. It only manifests when someone is customizing the generated code. <!--- Please describe in detail how you tested your changes --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ Co-authored-by: Gustavo Muenz <muenze@amazon.com>
1 parent 0c72716 commit dcf16ac

File tree

5 files changed

+148
-3
lines changed

5 files changed

+148
-3
lines changed

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ class Attribute(val inner: Writable, val isDeriveHelper: Boolean = false) {
560560
val AllowUnusedMut = Attribute(allow("unused_mut"))
561561
val AllowUnusedVariables = Attribute(allow("unused_variables"))
562562
val CfgTest = Attribute(cfg("test"))
563+
val DenyDeprecated = Attribute(deny("deprecated"))
563564
val DenyMissingDocs = Attribute(deny("missing_docs"))
564565
val DocHidden = Attribute(doc("hidden"))
565566
val DocInline = Attribute(doc("inline"))

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ class BuilderGenerator(
189189
metadata.derives.filter {
190190
it == RuntimeType.Debug || it == RuntimeType.PartialEq || it == RuntimeType.Clone
191191
} + RuntimeType.Default
192+
193+
// Filter out attributes
194+
private val builderAttributes =
195+
metadata.additionalAttributes.filter {
196+
it == Attribute.NonExhaustive
197+
}
192198
private val builderName = symbolProvider.symbolForBuilder(shape).name
193199

194200
fun render(writer: RustWriter) {
@@ -306,8 +312,8 @@ class BuilderGenerator(
306312

307313
private fun renderBuilder(writer: RustWriter) {
308314
writer.docs("A builder for #D.", structureSymbol)
309-
metadata.additionalAttributes.render(writer)
310315
Attribute(derive(builderDerives)).render(writer)
316+
this.builderAttributes.render(writer)
311317
writer.rustBlock("pub struct $builderName") {
312318
for (member in members) {
313319
val memberName = symbolProvider.toMemberName(member)

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,11 +546,14 @@ fun RustCrate.integrationTest(
546546
writable: Writable,
547547
) = this.withFile("tests/$name.rs", writable)
548548

549-
fun TestWriterDelegator.unitTest(test: Writable): TestWriterDelegator {
549+
fun TestWriterDelegator.unitTest(
550+
additionalAttributes: List<Attribute> = emptyList(),
551+
test: Writable,
552+
): TestWriterDelegator {
550553
lib {
551554
val name = safeName("test")
552555
withInlineModule(RustModule.inlineTests(name), TestModuleDocProvider) {
553-
unitTest(name) {
556+
unitTest(name, additionalAttributes = additionalAttributes) {
554557
test(this)
555558
}
556559
}

codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import software.amazon.smithy.model.Model
1111
import software.amazon.smithy.model.knowledge.NullableIndex
1212
import software.amazon.smithy.model.shapes.Shape
1313
import software.amazon.smithy.model.shapes.StructureShape
14+
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
1415
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.AllowDeprecated
1516
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
1617
import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
@@ -19,6 +20,8 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
1920
import software.amazon.smithy.rust.codegen.core.smithy.Default
2021
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
2122
import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider
23+
import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata
24+
import software.amazon.smithy.rust.codegen.core.smithy.meta
2225
import software.amazon.smithy.rust.codegen.core.smithy.setDefault
2326
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
2427
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
@@ -240,4 +243,57 @@ internal class BuilderGeneratorTest {
240243
}
241244
project.compileAndTest()
242245
}
246+
247+
@Test
248+
fun `builder doesn't inherit attributes from struct`() {
249+
/**
250+
* This test checks if the generated Builder doesn't inherit the macro attributes added to the main struct.
251+
*
252+
* The strategy is to:
253+
* 1) mark the `Inner` struct with `#[deprecated]`
254+
* 2) deny use of deprecated in the test
255+
* 3) allow use of deprecated by the Builder
256+
* 4) Ensure that the builder can be instantiated
257+
*/
258+
class SymbolProviderWithExtraAnnotation(val base: RustSymbolProvider) : RustSymbolProvider by base {
259+
override fun toSymbol(shape: Shape): Symbol {
260+
val baseSymbol = base.toSymbol(shape)
261+
val name = baseSymbol.name
262+
if (name == "Inner") {
263+
var metadata = baseSymbol.expectRustMetadata()
264+
val attribute = Attribute.Deprecated
265+
metadata = metadata.copy(additionalAttributes = metadata.additionalAttributes + listOf(attribute))
266+
return baseSymbol.toBuilder().meta(metadata).build()
267+
} else {
268+
return baseSymbol
269+
}
270+
}
271+
}
272+
273+
val provider = SymbolProviderWithExtraAnnotation(testSymbolProvider(model))
274+
val project = TestWorkspace.testProject(provider)
275+
project.moduleFor(inner) {
276+
rust("##![allow(deprecated)]")
277+
generator(model, provider, this, inner).render()
278+
implBlock(provider.toSymbol(inner)) {
279+
BuilderGenerator.renderConvenienceMethod(this, provider, inner)
280+
}
281+
unitTest("test", additionalAttributes = listOf(Attribute.DenyDeprecated), block = {
282+
rust(
283+
// Notice that the builder is instantiated directly, and not through the Inner::builder() method.
284+
// This is because Inner is marked with `deprecated`, so any usage of `Inner` inside the test will
285+
// fail the compilation.
286+
//
287+
// This piece of code would fail though if the Builder inherits the attributes from Inner.
288+
"""
289+
let _ = test_inner::Builder::default();
290+
""",
291+
)
292+
})
293+
}
294+
project.withModule(provider.moduleForBuilder(inner)) {
295+
BuilderGenerator(model, provider, inner, emptyList()).render(this)
296+
}
297+
project.compileAndTest()
298+
}
243299
}

codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
package software.amazon.smithy.rust.codegen.server.smithy.generators
77

88
import org.junit.jupiter.api.Test
9+
import software.amazon.smithy.codegen.core.Symbol
10+
import software.amazon.smithy.model.shapes.Shape
911
import software.amazon.smithy.model.shapes.StructureShape
12+
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
1013
import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
1114
import software.amazon.smithy.rust.codegen.core.rustlang.rust
15+
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
16+
import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata
1217
import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator
18+
import software.amazon.smithy.rust.codegen.core.smithy.meta
1319
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
1420
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
1521
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
@@ -79,4 +85,77 @@ class ServerBuilderGeneratorTest {
7985
}
8086
project.compileAndTest()
8187
}
88+
89+
@Test
90+
fun `builder doesn't inherit attributes from struct`() {
91+
/**
92+
* This test checks if the generated Builder doesn't inherit the macro attributes added to the main struct.
93+
*
94+
* The strategy is to:
95+
* 1) mark the `Inner` struct with `#[deprecated]`
96+
* 2) deny use of deprecated in the test
97+
* 3) allow use of deprecated by the Builder
98+
* 4) Ensure that the builder can be instantiated
99+
*/
100+
val model =
101+
"""
102+
namespace test
103+
104+
structure Inner {}
105+
""".asSmithyModel()
106+
107+
class SymbolProviderWithExtraAnnotation(val base: RustSymbolProvider) : RustSymbolProvider by base {
108+
override fun toSymbol(shape: Shape): Symbol {
109+
val baseSymbol = base.toSymbol(shape)
110+
val name = baseSymbol.name
111+
if (name == "Inner") {
112+
var metadata = baseSymbol.expectRustMetadata()
113+
val attribute = Attribute.Deprecated
114+
metadata = metadata.copy(additionalAttributes = metadata.additionalAttributes + listOf(attribute))
115+
return baseSymbol.toBuilder().meta(metadata).build()
116+
} else {
117+
return baseSymbol
118+
}
119+
}
120+
}
121+
122+
val codegenContext = serverTestCodegenContext(model)
123+
val provider = SymbolProviderWithExtraAnnotation(codegenContext.symbolProvider)
124+
val project = TestWorkspace.testProject(provider)
125+
project.withModule(ServerRustModule.Model) {
126+
val shape = model.lookup<StructureShape>("test#Inner")
127+
val writer = this
128+
129+
rust("##![allow(deprecated)]")
130+
StructureGenerator(model, provider, writer, shape, emptyList(), codegenContext.structSettings()).render()
131+
val builderGenerator =
132+
ServerBuilderGenerator(
133+
codegenContext,
134+
shape,
135+
SmithyValidationExceptionConversionGenerator(codegenContext),
136+
ServerRestJsonProtocol(codegenContext),
137+
)
138+
139+
builderGenerator.render(project, writer)
140+
141+
writer.implBlock(provider.toSymbol(shape)) {
142+
builderGenerator.renderConvenienceMethod(this)
143+
}
144+
145+
project.renderInlineMemoryModules()
146+
}
147+
project.unitTest(additionalAttributes = listOf(Attribute.DenyDeprecated), test = {
148+
rust(
149+
// Notice that the builder is instantiated directly, and not through the Inner::builder() method.
150+
// This is because Inner is marked with `deprecated`, so any usage of `Inner` inside the test will
151+
// fail the compilation.
152+
//
153+
// This piece of code would fail though if the Builder inherits the attributes from Inner.
154+
"""
155+
let _ = crate::model::inner::Builder::default();
156+
""",
157+
)
158+
})
159+
project.compileAndTest()
160+
}
82161
}

0 commit comments

Comments
 (0)