From a7519ea72816952a3c20cf30c3d5b572af86b4aa Mon Sep 17 00:00:00 2001
From: Milan Kriz <milan.kriz@eccam.com>
Date: Mon, 21 Oct 2024 17:50:01 +0200
Subject: [PATCH] structure data initializer, fix optionals

---
 extension/freemarker/CompoundField.inc.ftl    | 48 ++++++---
 extension/freemarker/Structure.cpp.ftl        | 34 ++++---
 extension/freemarker/Structure.h.ftl          |  5 +-
 runtime/src/zserio/Bitmasks.h                 |  4 +-
 runtime/src/zserio/Enums.h                    |  4 +-
 runtime/src/zserio/HashCodeUtil.h             | 27 +++++
 test/language/default_values/CMakeLists.txt   | 24 +++++
 .../default_values/ClangTidySuppressions.txt  |  9 ++
 .../cpp/StructureDefaultValuesTest.cpp        | 98 +++++++++++++++++++
 9 files changed, 219 insertions(+), 34 deletions(-)
 create mode 100644 test/language/default_values/CMakeLists.txt
 create mode 100644 test/language/default_values/ClangTidySuppressions.txt
 create mode 100644 test/language/default_values/cpp/StructureDefaultValuesTest.cpp

diff --git a/extension/freemarker/CompoundField.inc.ftl b/extension/freemarker/CompoundField.inc.ftl
index 0a80b29..0b3790f 100644
--- a/extension/freemarker/CompoundField.inc.ftl
+++ b/extension/freemarker/CompoundField.inc.ftl
@@ -6,6 +6,14 @@
     </#if>
 </#macro>
 
+<#macro field_data_member_type_name field>
+    <#if field.optional??>
+        ::zserio::Optional<<@field_data_type_name field/>><#t>
+    <#else>
+        <@field_data_type_name field/><#t>
+    </#if>
+</#macro>
+
 <#macro field_data_arg_name field>
     ${field.name}_<#t>
 </#macro>
@@ -14,25 +22,24 @@
     ${field.name}<#t>
 </#macro>
 
-<#macro field_view_type_name field>
-    <#if field.typeInfo.isSimple>
-        ${field.typeInfo.typeFullName}<#t>
-    <#elseif field.typeInfo.isString>
-        ::std::string_view<#t>
-    <#elseif field.typeInfo.isExtern>
-        const ${types.bitBuffer.name}&<#t>
-    <#elseif field.typeInfo.isBytes>
-        ::zserio::Span<const uint8_t><#t>
-    <#else>
-        View<${field.typeInfo.typeFullName}><#t>
-    </#if>
-</#macro>
-
 <#macro field_view_getter_type_name field>
+    <#local typeName>
+        <#if field.typeInfo.isSimple>
+            ${field.typeInfo.typeFullName}<#t>
+        <#elseif field.typeInfo.isString>
+            ::std::string_view<#t>
+        <#elseif field.typeInfo.isExtern>
+            const ${types.bitBuffer.name}&<#t>
+        <#elseif field.typeInfo.isBytes>
+            ::zserio::Span<const uint8_t><#t>
+        <#else>
+            View<${field.typeInfo.typeFullName}><#t>
+        </#if>
+    </#local>
     <#if field.optional??>
-    ::zserio::Optional<<@field_view_type_name field/>><#t>
+        ::zserio::Optional<${typeName}><#t>
     <#else>
-    <@field_view_type_name field/><#t>
+        ${typeName}<#t>
     </#if>
 </#macro>
 
@@ -52,6 +59,15 @@
     CHOICE_${field.name}<#t>
 </#macro>
 
+<#function has_optional_field fieldList>
+    <#list fieldList as field>
+        <#if field.optional??>
+            <#return true>
+        </#if>
+    </#list>
+    <#return false>
+</#function>
+
 <#function needs_allocator fieldList>
     <#list fieldList as field>
         <#if field.needsAllocator>
diff --git a/extension/freemarker/Structure.cpp.ftl b/extension/freemarker/Structure.cpp.ftl
index 23799c7..9d7e732 100644
--- a/extension/freemarker/Structure.cpp.ftl
+++ b/extension/freemarker/Structure.cpp.ftl
@@ -19,9 +19,13 @@ ${name}::${name}() noexcept :
 ${name}::${name}(const allocator_type&<#if needs_allocator(fieldList)> allocator</#if>) noexcept<#rt>
 <#list fieldList>
         <#lt> :
-        <#items as field>
+    <#items as field>
+        <#if field.initializer??>
+        ${field.name}(${field.initializer}<#if field.needsAllocator>, allocator</#if>)<#sep>,</#sep>
+        <#else>
         ${field.name}(<#if field.needsAllocator>allocator</#if>)<#sep>,</#sep>
-        </#items>
+        </#if>
+    </#items>
 <#else>
 
 </#list>
@@ -169,28 +173,32 @@ bool operator==(const View<${fullName}>&<#if fieldList?has_content || parameterL
 </#if>
 }
 
-<#macro structure_view_field_less_than lhs rhs indent>
+<#macro structure_view_field_less_than field indent>
     <#local I>${""?left_pad(indent * 4)}</#local>
-${I}if (${lhs} != ${rhs})
+${I}if (<#if field.optional??>*</#if>lhs.${field.getterName}() != <#if field.optional??>*</#if>rhs.${field.getterName}())
 ${I}{
-${I}    return ${lhs} < ${rhs};
+    <#if field.typeInfo.isBoolean>
+        <#-- TODO[Mi-L@]: Remove once operator< for zserio::Bool is implemented in runtime! -->
+${I}    return static_cast<int>(<#if field.optional??>*</#if>lhs.${field.getterName}()) < <#rt>
+        <#lt>static_cast<int>(<#if field.optional??>*</#if>rhs.${field.getterName}());
+    <#else>
+${I}    return <#if field.optional??>*</#if>lhs.${field.getterName}() < <#if field.optional??>*</#if>rhs.${field.getterName}();
+    </#if>
 ${I}}
 </#macro>
 <#macro structure_view_field_less_than_optional field indent>
     <#local I>${""?left_pad(indent * 4)}</#local>
     <#if field.optional??>
-${I}if (${field.getterName}() && other.${field.getterName}())
+${I}if (lhs.${field.getterName}() && rhs.${field.getterName}())
 ${I}{
-        <@structure_view_field_less_than "*lhs." + field.getterName + "()", "*rhs." + field.getterName + "()",
-                indent+1/>
+        <@structure_view_field_less_than field, indent+1/>
 ${I}}
-${I}else if (${field.getterName}() != other.${field.getterName}())
+${I}else if (lhs.${field.getterName}() != rhs.${field.getterName}())
 ${I}{
-${I}    return !${field.getterName}();
+${I}    return !lhs.${field.getterName}();
 ${I}}
     <#else>
-    <@structure_view_field_less_than "lhs." + field.getterName + "()", "rhs." + field.getterName + "()",
-            indent/>
+    <@structure_view_field_less_than field indent/>
     </#if>
 </#macro>
 bool operator<(const View<${fullName}>&<#if fieldList?has_content || parameterList?has_content> lhs</#if>, <#rt>
@@ -271,7 +279,7 @@ ${I}in.alignTo(${field.alignmentValue});
 ${I}in.alignTo(8);
     </#if>
     <#local compoundArguments><@field_view_parameters field/></#local>
-${I}(void)::zserio::detail::read(reader, data.<@field_data_member_name field/><#rt>
+${I}(void)::zserio::detail::read(reader, <#if field.optional??>*</#if>data.<@field_data_member_name field/><#rt>
         <#lt><#if compoundArguments?has_content>, ${compoundArguments}</#if>);
 </#macro>
 View<${fullName}> read(::zserio::BitStreamReader&<#if fieldList?has_content> reader</#if>, <#rt>
diff --git a/extension/freemarker/Structure.h.ftl b/extension/freemarker/Structure.h.ftl
index 626f6be..dfce39d 100644
--- a/extension/freemarker/Structure.h.ftl
+++ b/extension/freemarker/Structure.h.ftl
@@ -10,6 +10,9 @@
 
 #include <memory>
 #include <zserio/View.h>
+<#if has_optional_field(fieldList)>
+#include <zserio/Optional.h>
+</#if>
 <@system_includes headerSystemIncludes/>
 <@user_includes headerUserIncludes/>
 <@namespace_begin package.path/>
@@ -32,7 +35,7 @@ struct ${name}
 </#list>
 
 <#list fieldList as field>
-    <@field_data_type_name field/> <@field_data_member_name field/>;
+    <@field_data_member_type_name field/> <@field_data_member_name field/>;
 </#list>
 };
 
diff --git a/runtime/src/zserio/Bitmasks.h b/runtime/src/zserio/Bitmasks.h
index cedb3bd..51229df 100644
--- a/runtime/src/zserio/Bitmasks.h
+++ b/runtime/src/zserio/Bitmasks.h
@@ -13,9 +13,9 @@ namespace detail
 {
 
 template <typename T>
-std::enable_if_t<is_bitmask_v<T>, BitSize> bitSizeOf(T value)
+std::enable_if_t<is_bitmask_v<T>, BitSize> bitSizeOf(T value, BitSize bitPosition = 0)
 {
-    return bitSizeOf(value.getValue());
+    return bitSizeOf(value.getValue(), bitPosition);
 }
 
 template <typename T>
diff --git a/runtime/src/zserio/Enums.h b/runtime/src/zserio/Enums.h
index b806a94..9905bd7 100644
--- a/runtime/src/zserio/Enums.h
+++ b/runtime/src/zserio/Enums.h
@@ -99,9 +99,9 @@ namespace detail
 {
 
 template <typename T>
-inline std::enable_if_t<std::is_enum_v<T>, BitSize> bitSizeOf(T value)
+inline std::enable_if_t<std::is_enum_v<T>, BitSize> bitSizeOf(T value, BitSize bitPosition = 0)
 {
-    return bitSizeOf(enumToValue(value));
+    return bitSizeOf(enumToValue(value), bitPosition);
 }
 
 template <typename T>
diff --git a/runtime/src/zserio/HashCodeUtil.h b/runtime/src/zserio/HashCodeUtil.h
index ff5727a..2195d4c 100644
--- a/runtime/src/zserio/HashCodeUtil.h
+++ b/runtime/src/zserio/HashCodeUtil.h
@@ -101,6 +101,33 @@ inline uint32_t calcHashCode(uint32_t seedValue, detail::IntWrapper<VALUE_TYPE,
     return calcHashCode(seedValue, static_cast<VALUE_TYPE>(value));
 }
 
+/**
+ * Calculates hash code of the given bool wrapper value using the given seed value.
+ *
+ * \param seedValue Seed value (current hash code).
+ * \param value Value for which to calculate the hash code.
+ *
+ * \return Calculated hash code.
+ */
+inline uint32_t calcHashCode(uint32_t seedValue, Bool value)
+{
+    return calcHashCode(seedValue, static_cast<Bool::value_type>(value));
+}
+
+/**
+ * Calculates hash code of the given floating-point wrapper value using the given seed value.
+ *
+ * \param seedValue Seed value (current hash code).
+ * \param value Value for which to calculate the hash code.
+ *
+ * \return Calculated hash code.
+ */
+template <typename VALUE_TYPE, detail::FloatType FLOAT_TYPE>
+inline uint32_t calcHashCode(uint32_t seedValue, detail::FloatWrapper<VALUE_TYPE, FLOAT_TYPE> value)
+{
+    return calcHashCode(seedValue, static_cast<VALUE_TYPE>(value));
+}
+
 // TODO[Mi-L@]: support all zserio wrappers
 
 /**
diff --git a/test/language/default_values/CMakeLists.txt b/test/language/default_values/CMakeLists.txt
new file mode 100644
index 0000000..d1c64f8
--- /dev/null
+++ b/test/language/default_values/CMakeLists.txt
@@ -0,0 +1,24 @@
+add_library(default_values_zs STATIC ${TEST_ZS_ROOT}/default_values.zs)
+zserio_generate_cpp(
+    TARGET default_values_zs
+    SRC_DIR ${TEST_ZS_ROOT}
+    GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/gen
+    EXTRA_ARGS ${ZSERIO_EXTRA_ARGS}
+    GENERATED_SOURCES_VAR GENERATED_SOURCES
+    OUTPUT_VAR ZSERIO_LOG
+    ERROR_VAR ZSERIO_LOG
+)
+target_link_libraries(default_values_zs PUBLIC ZserioCpp17Runtime)
+if (ZSERIO_LOG)
+    file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/zserio_log.txt "${ZSERIO_LOG}")
+    check_zserio_warnings("${ZSERIO_LOG}" 0)
+endif ()
+
+add_custom_test(default_values
+    DEPENDS
+        default_values_zs
+    SOURCES
+        ${CMAKE_CURRENT_SOURCE_DIR}/cpp/StructureDefaultValuesTest.cpp
+    GENERATED_SOURCES
+        ${GENERATED_SOURCES}
+)
diff --git a/test/language/default_values/ClangTidySuppressions.txt b/test/language/default_values/ClangTidySuppressions.txt
new file mode 100644
index 0000000..aeac781
--- /dev/null
+++ b/test/language/default_values/ClangTidySuppressions.txt
@@ -0,0 +1,9 @@
+google-explicit-constructor:gen/default_values/structure_default_values/Permission.h
+
+#hicpp-signed-bitwise:gen/default_values/structure_default_values/Permission.h
+
+#readability-make-member-function-const:gen/default_values/structure_default_values/StructureDefaultValues.cpp
+
+readability-uppercase-literal-suffix:gen/default_values/structure_default_values/StructureDefaultValues.cpp
+
+readability-simplify-boolean-expr:gen/default_values/structure_default_values/StructureDefaultValues.cpp
diff --git a/test/language/default_values/cpp/StructureDefaultValuesTest.cpp b/test/language/default_values/cpp/StructureDefaultValuesTest.cpp
new file mode 100644
index 0000000..9271116
--- /dev/null
+++ b/test/language/default_values/cpp/StructureDefaultValuesTest.cpp
@@ -0,0 +1,98 @@
+#include "default_values/structure_default_values/BasicColor.h"
+#include "default_values/structure_default_values/Permission.h"
+#include "default_values/structure_default_values/StructureDefaultValues.h"
+#include "gtest/gtest.h"
+#include "zserio/BitStreamReader.h"
+#include "zserio/CppRuntimeException.h"
+
+namespace default_values
+{
+namespace structure_default_values
+{
+
+TEST(StructureDefaultValuesDataTest, checkDefaultBoolValue)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ(true, structureDefaultValues.boolValue);
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultBit4Value)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ(0x0F, structureDefaultValues.bit4Value);
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultInt16Value)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ(0x0BEE, structureDefaultValues.int16Value);
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultFloat16Value)
+{
+    StructureDefaultValues structureDefaultValues;
+    float diff = 1.23F - structureDefaultValues.float16Value;
+    if (diff < 0.0F)
+    {
+        diff = -diff;
+    }
+    ASSERT_TRUE(diff <= std::numeric_limits<float>::epsilon());
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultFloat32Value)
+{
+    StructureDefaultValues structureDefaultValues;
+    float diff = 1.234F - structureDefaultValues.float32Value;
+    if (diff < 0.0F)
+    {
+        diff = -diff;
+    }
+    ASSERT_TRUE(diff <= std::numeric_limits<float>::epsilon());
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultFloat64Value)
+{
+    StructureDefaultValues structureDefaultValues;
+    double diff = 1.2345 - structureDefaultValues.float64Value;
+    if (diff < 0.0)
+    {
+        diff = -diff;
+    }
+    ASSERT_TRUE(diff <= std::numeric_limits<double>::epsilon());
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultStringValue)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ("string", structureDefaultValues.stringValue);
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultEnumValue)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ(BasicColor::BLACK, structureDefaultValues.enumValue);
+}
+
+TEST(StructureDefaultValuesDataTest, checkDefaultBitmaskValue)
+{
+    StructureDefaultValues structureDefaultValues;
+    ASSERT_EQ(Permission::Values::READ_WRITE, structureDefaultValues.bitmaskValue);
+}
+
+TEST(StructureDefaultValuesDataTest, operatorEquality)
+{
+    StructureDefaultValues structureDefaultValues1;
+    StructureDefaultValues structureDefaultValues2;
+    ASSERT_EQ(structureDefaultValues1, structureDefaultValues2);
+}
+
+TEST(StructureDefaultValuesDataTest, operatorLessThan)
+{
+    StructureDefaultValues structureDefaultValues1;
+    StructureDefaultValues structureDefaultValues2;
+    ASSERT_FALSE(structureDefaultValues1 < structureDefaultValues2);
+    ASSERT_FALSE(structureDefaultValues2 < structureDefaultValues1);
+}
+
+} // namespace structure_default_values
+} // namespace default_values