Repository: smithy-lang/smithy
Pull request #2652 (2025-05-30 20:59:40)

Fix trait code generation for collections of boolean.
#### Background
TraitCodegen will generate incorrect `fromNode` code for nested collection like the following:
```java
public static NestedListTrait fromNode(Node node) {
        Builder builder = builder();
        node.expectArrayNode()
            .getElements().stream()
            .map(n -> n.expectArrayNode()
                .getElements().stream()
                .map(n1 -> n1.expectArrayNode()
                    .getElements().stream()
                    .map(n12 -> BooleanMember("n12", builder::n12))
                    .collect(Collectors.toList()))
                .collect(Collectors.toList()))
            .forEach(builder::addValues);
        return builder.build();
}
```
#### Fix
Fixed the incorrect code generated for boolean member in nested collection.
Fixed the problematic lambda variable name like `n12`.
Fixed no new line starting for `.sourceLocation()` `in createNode()` for structure based trait.

The code generated after fix:
```java
public static NestedBooleanListTrait fromNode(Node node) {
        Builder builder = builder();
        node.expectArrayNode()
            .getElements().stream()
            .map(n -> n.expectArrayNode()
                .getElements().stream()
                .map(n1 -> n1.expectArrayNode()
                    .getElements().stream()
                    .map(n2 -> n2.expectBooleanNode().getValue())
                    .collect(Collectors.toList()))
                .collect(Collectors.toList()))
            .forEach(builder::addValues);
        return builder.build();
}
```

```java
// StructureWithListOfMapTrait
@Override
protected Node createNode() {
        return Node.objectNodeBuilder()
            .sourceLocation(getSourceLocation())
            .withOptionalMember("name", getName().map(m -> Node.from(m)))
            .withOptionalMember("items", getItems().map(m -> m.stream().map(s -> s.entrySet().stream()
                .map(entry1 -> new SimpleImmutableEntry<>(
                    Node.from(entry1.getKey()), Node.from(entry1.getValue())))
                .collect(ObjectNode.collect(Entry::getKey, Entry::getValue))
            ).collect(ArrayNode.collect())))
            .build();
}
```

#### Links
* Issue #2651 

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Patch:
diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java
index e8c6d2e53..d80dd700b 100644
--- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java
+++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java
@@ -58,7 +58,7 @@ final class FromNodeMapperVisitor extends ShapeVisitor.DataShapeVisitor<Void> {
 
     @Override
     public Void booleanShape(BooleanShape shape) {
-        writer.write("BooleanMember($1S, builder::$1L)", varName);
+        writer.write("$L.expectBooleanNode().getValue()", varName);
         return null;
     }
 
@@ -67,22 +67,17 @@ final class FromNodeMapperVisitor extends ShapeVisitor.DataShapeVisitor<Void> {
         writer.write("$L.expectArrayNode()", varName);
         writer.indent();
         writer.writeWithNoFormatting(".getElements().stream()");
+        String argName = "n" + (nestedLevel > 0 ? Integer.toString(nestedLevel) : "");
+        writer.write(".map($L -> $C)",
+                argName,
+                (Runnable) () -> shape.getMember()
+                        .accept(new FromNodeMapperVisitor(writer,
+                                model,
+                                argName,
+                                nestedLevel + 1)));
         if (nestedLevel == 0) { // Triggered when shape is a member.
-            writer.write(".map(n -> $C)",
-                    (Runnable) () -> shape.getMember()
-                            .accept(new FromNodeMapperVisitor(writer,
-                                    model,
-                                    "n",
-                                    nestedLevel + 1)));
             writer.writeWithNoFormatting(".forEach(builder::addValues);");
         } else { // Triggered when shape is nested.
-            writer.write(".map($L -> $C)",
-                    varName + nestedLevel,
-                    (Runnable) () -> shape.getMember()
-                            .accept(new FromNodeMapperVisitor(writer,
-                                    model,
-                                    varName + nestedLevel,
-                                    nestedLevel + 1)));
             writer.write(".collect($T.toList())", Collectors.class);
         }
         writer.dedent();
diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java
index cb9fa7aab..be5e89431 100644
--- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java
+++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java
@@ -139,7 +139,7 @@ final class ToNodeGenerator implements Runnable {
             if (shape.hasTrait(TraitDefinition.ID)) {
                 // If the shape is a trait we need to add the source location of trait to the
                 // generated node.
-                writer.writeInline(".sourceLocation(getSourceLocation())");
+                writer.writeWithNoFormatting(".sourceLocation(getSourceLocation())");
             }
             for (MemberShape mem : shape.members()) {
                 if (TraitCodegenUtils.isNullableMember(mem)) {


Test patch:
diff --git a/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/lists/nested-boolean-list-trait.smithy b/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/lists/nested-boolean-list-trait.smithy
new file mode 100644
index 000000000..6bdc36067
--- /dev/null
+++ b/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/lists/nested-boolean-list-trait.smithy
@@ -0,0 +1,8 @@
+$version: "2.0"
+
+namespace test.smithy.traitcodegen
+
+use test.smithy.traitcodegen.lists#NestedBooleanListTrait
+
+@NestedBooleanListTrait([[[true]]])
+structure myStruct {}
diff --git a/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java b/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java
index d9e896141..276b27761 100644
--- a/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java
+++ b/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java
@@ -26,7 +26,7 @@ import software.amazon.smithy.model.node.ArrayNode;
 import software.amazon.smithy.model.node.ObjectNode;
 
 public class TraitCodegenPluginTest {
-    private static final int EXPECTED_NUMBER_OF_FILES = 63;
+    private static final int EXPECTED_NUMBER_OF_FILES = 64;
 
     private MockManifest manifest;
     private Model model;
diff --git a/smithy-trait-codegen/src/test/resources/META-INF/smithy/lists/nested-boolean-list-trait.smithy b/smithy-trait-codegen/src/test/resources/META-INF/smithy/lists/nested-boolean-list-trait.smithy
new file mode 100644
index 000000000..7801d969e
--- /dev/null
+++ b/smithy-trait-codegen/src/test/resources/META-INF/smithy/lists/nested-boolean-list-trait.smithy
@@ -0,0 +1,16 @@
+$version: "2.0"
+
+namespace test.smithy.traitcodegen.lists
+
+@trait
+list NestedBooleanListTrait {
+    member: BooleanItemsList
+}
+
+list BooleanItemsList {
+    member: BooleanItemsListEntry
+}
+
+list BooleanItemsListEntry {
+    member: Boolean
+}
diff --git a/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest b/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest
index 595933e3c..6232680b8 100644
--- a/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest
+++ b/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest
@@ -16,6 +16,7 @@ lists/string-list-trait.smithy
 lists/struct-list-trait.smithy
 lists/document-list-trait.smithy
 lists/nested-list-trait.smithy
+lists/nested-boolean-list-trait.smithy
 maps/string-string-map-trait.smithy
 maps/string-to-struct-map-trait.smithy
 maps/string-to-document-map-trait.smithy