Skip to content

Commit 66692b3

Browse files
drganjooFahad Zubair
and
Fahad Zubair
authored
Merge documentation of same inline modules (#4008)
When two inline modules with the same name are generated from different parts of the codebase, their documentation should be merged. However, other metadata must match exactly, as it is an error for one part of the codebase to define an inline module with pub visibility while another defines it with pub(crate) visibility. This PR enables documentation merging while maintaining strict validation of other metadata fields. Currently, the following sample model fails to generate code because both `SomeList` and `member` are generated in the same inline module but with different doc comments: ```smithy @documentation("Outer constraint has some documentation") @Length(max: 3) list SomeList { @Length(max: 8000) member: String } ``` Co-authored-by: Fahad Zubair <[email protected]>
1 parent 77e8062 commit 66692b3

File tree

2 files changed

+95
-11
lines changed

2 files changed

+95
-11
lines changed

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/RustCrateInlineModuleComposingWriter.kt

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -352,25 +352,74 @@ class InnerModule(private val moduleDocProvider: ModuleDocProvider, debugMode: B
352352
inlineModuleList: MutableList<InlineModuleWithWriter>,
353353
lookForModule: RustModule.LeafModule,
354354
): RustWriter {
355-
val inlineModuleAndWriter =
356-
inlineModuleList.firstOrNull {
357-
it.inlineModule.name == lookForModule.name
358-
}
359-
return if (inlineModuleAndWriter == null) {
355+
val index = inlineModuleList.indexOfFirst { it.inlineModule.name == lookForModule.name }
356+
357+
return if (index == -1) {
360358
val inlineWriter = createNewInlineModule()
361359
inlineModuleList.add(InlineModuleWithWriter(lookForModule, inlineWriter))
362360
inlineWriter
363361
} else {
364-
check(inlineModuleAndWriter.inlineModule == lookForModule) {
365-
"""The two inline modules have the same name but different attributes on them:
366-
1) ${inlineModuleAndWriter.inlineModule}
367-
2) $lookForModule"""
362+
val existing = inlineModuleList[index]
363+
364+
// Verify everything except documentation matches.
365+
val existingModule = existing.inlineModule
366+
check(
367+
existingModule.name == lookForModule.name &&
368+
existingModule.rustMetadata == lookForModule.rustMetadata &&
369+
existingModule.parent == lookForModule.parent &&
370+
existingModule.tests == lookForModule.tests,
371+
) {
372+
"""
373+
An inline module with the same name `${lookForModule.name}` was earlier created with different attributes:
374+
1) Metadata:
375+
`${existingModule.rustMetadata}`
376+
`${lookForModule.rustMetadata}`
377+
2) Parent:
378+
`${existingModule.parent}`
379+
`${lookForModule.parent}`
380+
3) Tests:
381+
`${existingModule.tests}`
382+
`${lookForModule.tests}`
383+
4) DocumentationOverride:
384+
`${existingModule.documentationOverride}`
385+
`${lookForModule.documentationOverride}`
386+
"""
368387
}
369388

370-
inlineModuleAndWriter.writer
389+
// Merge documentation.
390+
val mergedDoc =
391+
mergeDocumentation(existingModule.documentationOverride, lookForModule.documentationOverride)
392+
393+
// Replace the element in the list with merged documentation.
394+
inlineModuleList[index] =
395+
InlineModuleWithWriter(
396+
existingModule.copy(documentationOverride = mergedDoc),
397+
existing.writer,
398+
)
399+
400+
existing.writer
371401
}
372402
}
373403

404+
// @VisibleForTesting
405+
internal fun mergeDocumentation(
406+
existingModule: String?,
407+
lookForModule: String?,
408+
): String? {
409+
val mergedDoc =
410+
when {
411+
existingModule == null && lookForModule == null -> null
412+
existingModule == null -> lookForModule
413+
lookForModule == null -> existingModule
414+
else ->
415+
listOf(
416+
existingModule.trim(),
417+
lookForModule.trim(),
418+
).joinToString("\n")
419+
}
420+
return mergedDoc?.trim()
421+
}
422+
374423
private fun writeDocs(innerModule: RustModule.LeafModule) {
375424
docWriters[innerModule]?.forEach {
376425
it()

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import software.amazon.smithy.model.SourceLocation
1414
import software.amazon.smithy.model.shapes.ShapeId
1515
import software.amazon.smithy.model.traits.RequiredTrait
1616
import software.amazon.smithy.model.traits.Trait
17+
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
1718
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
19+
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
20+
import software.amazon.smithy.rust.codegen.core.smithy.ModuleDocProvider
1821
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
1922
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
2023
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
@@ -87,6 +90,7 @@ class ConstraintsMemberShapeTest {
8790
@pattern("^[g-m]+${'$'}")
8891
constrainedPatternString : PatternString
8992
93+
constrainedList : ConstrainedList
9094
plainStringList : PlainStringList
9195
patternStringList : PatternStringList
9296
patternStringListOverride : PatternStringListOverride
@@ -118,6 +122,11 @@ class ConstraintsMemberShapeTest {
118122
@range(min: 10, max:100)
119123
long : RangedInteger
120124
}
125+
@length(max: 3)
126+
list ConstrainedList {
127+
@length(max: 8000)
128+
member: String
129+
}
121130
list PlainStringList {
122131
member: String
123132
}
@@ -285,7 +294,12 @@ class ConstraintsMemberShapeTest {
285294

286295
@Test
287296
fun `generate code and check member constrained shapes are in the right modules`() {
288-
serverIntegrationTest(sampleModel, IntegrationTestParams(service = "constrainedMemberShape#ConstrainedService")) { codegenContext, rustCrate ->
297+
serverIntegrationTest(
298+
sampleModel,
299+
IntegrationTestParams(
300+
service = "constrainedMemberShape#ConstrainedService",
301+
),
302+
) { _, rustCrate ->
289303
fun RustWriter.testTypeExistsInBuilderModule(typeName: String) {
290304
unitTest(
291305
"builder_module_has_${typeName.toSnakeCase()}",
@@ -347,6 +361,27 @@ class ConstraintsMemberShapeTest {
347361
}
348362
}
349363

364+
@Test
365+
fun `merging docs should not produce extra empty lines`() {
366+
val docWriter =
367+
object : ModuleDocProvider {
368+
override fun docsWriter(module: RustModule.LeafModule): Writable? = null
369+
}
370+
val innerModule = InnerModule(docWriter, false)
371+
innerModule.mergeDocumentation("\n\n", "") shouldBe ""
372+
innerModule.mergeDocumentation(null, null) shouldBe null
373+
innerModule.mergeDocumentation(null, "some docs\n") shouldBe "some docs"
374+
innerModule.mergeDocumentation("some docs\n", null) shouldBe "some docs"
375+
innerModule.mergeDocumentation(null, "some docs") shouldBe "some docs"
376+
innerModule.mergeDocumentation("some docs", null) shouldBe "some docs"
377+
innerModule.mergeDocumentation(null, "some docs\n\n") shouldBe "some docs"
378+
innerModule.mergeDocumentation("some docs\n\n", null) shouldBe "some docs"
379+
innerModule.mergeDocumentation(null, "some docs\n\n") shouldBe "some docs"
380+
innerModule.mergeDocumentation("left side", "right side") shouldBe "left side\nright side"
381+
innerModule.mergeDocumentation("left side\n", "right side\n") shouldBe "left side\nright side"
382+
innerModule.mergeDocumentation("left side\n\n\n", "right side\n\n") shouldBe "left side\nright side"
383+
}
384+
350385
/**
351386
* Checks that the given member shape:
352387
* 1. Has been changed to a new shape

0 commit comments

Comments
 (0)