You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix recursive constraint violations with paths over list and map shapes (#2371)
* Fix recursive constraint violations with paths over list and map shapes
There is a widespread assumption throughout the generation of constraint
violations that does not hold true all the time, namely, that a
recursive constraint violation graph has the same requirements with
regards to boxing as the regular shape graph.
Some types corresponding to recursive shapes are boxed to introduce
indirection and thus not generate an infinitely recursive type. The
algorithm however does not superfluously introduce boxes when the cycle
goes through a list shape or a map shape. Why list shapes and map
shapes? List shapes and map shapes get rendered in Rust as `Vec<T>` and
`HashMap<K, V>`, respectively, they're the only Smithy shapes that
"organically" introduce indirection (via a pointer to the heap) in the
recursive path. For other recursive paths, we thus have to introduce the
indirection artificially ourselves using `Box`. This is done in the
`RecursiveShapeBoxer` model transform.
However, the constraint violation graph needs to box types in recursive
paths more often. Since we don't collect constraint violations
(yet, see #2040), the constraint violation graph never holds
`Vec<T>`s or `HashMap<K, V>`s, only simple types. Indeed, the following simple
recursive model:
```smithy
union Recursive {
list: List
}
@Length(min: 69)
list List {
member: Recursive
}
```
has a cycle that goes through a list shape, so no shapes in it need
boxing in the regular shape graph. However, the constraint violation
graph is infinitely recursive if we don't introduce boxing somewhere:
```rust
pub mod model {
pub mod list {
pub enum ConstraintViolation {
Length(usize),
Member(
usize,
crate::model::recursive::ConstraintViolation,
),
}
}
pub mod recursive {
pub enum ConstraintViolation {
List(crate::model::list::ConstraintViolation),
}
}
}
```
This commit fixes things by making the `RecursiveShapeBoxer` model
transform configurable so that the "cycles through lists and maps
introduce indirection" assumption can be lifted. This allows a server
model transform, `RecursiveConstraintViolationBoxer`, to tag member
shapes along recursive paths with a new trait,
`ConstraintViolationRustBoxTrait`, that the constraint violation type
generation then utilizes to ensure that no infinitely recursive
constraint violation types get generated.
For example, for the above model, the generated Rust code would now look
like:
```rust
pub mod model {
pub mod list {
pub enum ConstraintViolation {
Length(usize),
Member(
usize,
std::boxed::Box(crate::model::recursive::ConstraintViolation),
),
}
}
pub mod recursive {
pub enum ConstraintViolation {
List(crate::model::list::ConstraintViolation),
}
}
}
```
Likewise, places where constraint violations are handled (like where
unconstrained types are converted to constrained types) have been
updated to account for the scenario where they now are or need to be
boxed.
Parametrized tests have been added to exhaustively test combinations of
models exercising recursive paths going through (sparse and non-sparse)
list and map shapes, as well as union and structure shapes
(`RecursiveConstraintViolationsTest`). These tests even assert that the
specific member shapes along the cycles are tagged as expected
(`RecursiveConstraintViolationBoxerTest`).
* Address comments
Copy file name to clipboardExpand all lines: codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ResiliencyConfigCustomizationTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -36,7 +36,7 @@ internal class ResiliencyConfigCustomizationTest {
36
36
37
37
@Test
38
38
fun`generates a valid config`() {
39
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
39
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
40
40
val project =TestWorkspace.testProject()
41
41
val codegenContext = testCodegenContext(model, settings = project.rustSettings())
Copy file name to clipboardExpand all lines: codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapeBoxer.kt
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -327,7 +327,7 @@ class StructureGeneratorTest {
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/AwsQueryParserGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -42,7 +42,7 @@ class AwsQueryParserGeneratorTest {
42
42
43
43
@Test
44
44
fun`it modifies operation parsing to include Response and Result tags`() {
45
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
45
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
46
46
val codegenContext = testCodegenContext(model)
47
47
val symbolProvider = codegenContext.symbolProvider
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/Ec2QueryParserGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -42,7 +42,7 @@ class Ec2QueryParserGeneratorTest {
42
42
43
43
@Test
44
44
fun`it modifies operation parsing to include Response and Result tags`() {
45
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
45
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
46
46
val codegenContext = testCodegenContext(model)
47
47
val symbolProvider = codegenContext.symbolProvider
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -114,7 +114,7 @@ class JsonParserGeneratorTest {
114
114
115
115
@Test
116
116
fun`generates valid deserializers`() {
117
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
117
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
118
118
val codegenContext = testCodegenContext(model)
119
119
val symbolProvider = codegenContext.symbolProvider
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/XmlBindingTraitParserGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -94,7 +94,7 @@ internal class XmlBindingTraitParserGeneratorTest {
94
94
95
95
@Test
96
96
fun`generates valid parsers`() {
97
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
97
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
98
98
val codegenContext = testCodegenContext(model)
99
99
val symbolProvider = codegenContext.symbolProvider
100
100
val parserGenerator =XmlBindingTraitParserGenerator(
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/AwsQuerySerializerGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -92,7 +92,7 @@ class AwsQuerySerializerGeneratorTest {
92
92
true->CodegenTarget.CLIENT
93
93
false->CodegenTarget.SERVER
94
94
}
95
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
95
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
96
96
val codegenContext = testCodegenContext(model, codegenTarget = codegenTarget)
97
97
val symbolProvider = codegenContext.symbolProvider
98
98
val parserGenerator =AwsQuerySerializerGenerator(testCodegenContext(model, codegenTarget = codegenTarget))
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/Ec2QuerySerializerGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -85,7 +85,7 @@ class Ec2QuerySerializerGeneratorTest {
85
85
86
86
@Test
87
87
fun`generates valid serializers`() {
88
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
88
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
89
89
val codegenContext = testCodegenContext(model)
90
90
val symbolProvider = codegenContext.symbolProvider
91
91
val parserGenerator =Ec2QuerySerializerGenerator(codegenContext)
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -100,7 +100,7 @@ class JsonSerializerGeneratorTest {
100
100
101
101
@Test
102
102
fun`generates valid serializers`() {
103
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
103
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
104
104
val codegenContext = testCodegenContext(model)
105
105
val symbolProvider = codegenContext.symbolProvider
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGeneratorTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -105,7 +105,7 @@ internal class XmlBindingTraitSerializerGeneratorTest {
105
105
106
106
@Test
107
107
fun`generates valid serializers`() {
108
-
val model =RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
108
+
val model =RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
109
109
val codegenContext = testCodegenContext(model)
110
110
val symbolProvider = codegenContext.symbolProvider
111
111
val parserGenerator =XmlBindingTraitSerializerGenerator(
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapeBoxerTest.kt
+3-3
Original file line number
Diff line number
Diff line change
@@ -31,7 +31,7 @@ internal class RecursiveShapeBoxerTest {
31
31
hello: Hello
32
32
}
33
33
""".asSmithyModel()
34
-
RecursiveShapeBoxer.transform(model) shouldBe model
34
+
RecursiveShapeBoxer().transform(model) shouldBe model
35
35
}
36
36
37
37
@Test
@@ -43,7 +43,7 @@ internal class RecursiveShapeBoxerTest {
43
43
anotherField: Boolean
44
44
}
45
45
""".asSmithyModel()
46
-
val transformed =RecursiveShapeBoxer.transform(model)
46
+
val transformed =RecursiveShapeBoxer().transform(model)
47
47
val member:MemberShape= transformed.lookup("com.example#Recursive\$RecursiveStruct")
48
48
member.expectTrait<RustBoxTrait>()
49
49
}
@@ -70,7 +70,7 @@ internal class RecursiveShapeBoxerTest {
70
70
third: SecondTree
71
71
}
72
72
""".asSmithyModel()
73
-
val transformed =RecursiveShapeBoxer.transform(model)
73
+
val transformed =RecursiveShapeBoxer().transform(model)
74
74
val boxed = transformed.shapes().filter { it.hasTrait<RustBoxTrait>() }.toList()
Copy file name to clipboardExpand all lines: codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt
+1-1
Original file line number
Diff line number
Diff line change
@@ -66,7 +66,7 @@ class RecursiveShapesIntegrationTest {
66
66
}
67
67
output.message shouldContain "has infinite size"
68
68
69
-
val fixedProject = check(RecursiveShapeBoxer.transform(model))
69
+
val fixedProject = check(RecursiveShapeBoxer().transform(model))
Copy file name to clipboardExpand all lines: codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/CollectionConstraintViolationGenerator.kt
0 commit comments