diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index c72aca0cb..b50fbdd72 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -12,10 +12,12 @@ #include "behaviortree_cpp/basic_types.h" +#include #include #include #include #include +#include #include #include #include @@ -1147,10 +1149,59 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree( } else { - // constant string: just set that constant value into the BB + // constant value: set it into the BB with appropriate type // IMPORTANT: this must not be autoremapped!!! new_bb->enableAutoRemapping(false); - new_bb->set(attr_name, static_cast(attr_value)); + const std::string str_value(attr_value); + + // Try to preserve numeric types so that Script expressions + // can perform arithmetic without type-mismatch errors. + // Use std::from_chars with strict full-string validation to avoid + // false positives on compound strings like "1;2;3" or "2.2;2.4". + bool stored = false; + if(!str_value.empty()) + { + const char* begin = str_value.data(); + const char* end = begin + str_value.size(); + // Try integer first (no decimal point, no exponent notation). + // Use int when the value fits, to match the most common port + // declarations. Fall back to int64_t for larger values. + if(str_value.find('.') == std::string::npos && + str_value.find('e') == std::string::npos && + str_value.find('E') == std::string::npos) + { + int64_t int_val = 0; + auto [ptr, ec] = std::from_chars(begin, end, int_val); + if(ec == std::errc() && ptr == end) + { + if(int_val >= std::numeric_limits::min() && + int_val <= std::numeric_limits::max()) + { + new_bb->set(attr_name, static_cast(int_val)); + } + else + { + new_bb->set(attr_name, int_val); + } + stored = true; + } + } + // Try double + if(!stored) + { + double dbl_val = 0; + auto [ptr, ec] = std::from_chars(begin, end, dbl_val); + if(ec == std::errc() && ptr == end) + { + new_bb->set(attr_name, dbl_val); + stored = true; + } + } + } + if(!stored) + { + new_bb->set(attr_name, str_value); + } new_bb->enableAutoRemapping(do_autoremap); } } diff --git a/tests/gtest_subtree.cpp b/tests/gtest_subtree.cpp index 5fe450bee..cda5d68a8 100644 --- a/tests/gtest_subtree.cpp +++ b/tests/gtest_subtree.cpp @@ -974,3 +974,41 @@ TEST(SubTree, NestedDuplicateNames_ShouldFail) // Should throw RuntimeError because of duplicate SubTree names ASSERT_THROW((void)factory.createTreeFromText(xml_text), RuntimeError); } + +// Regression test: literal numeric values passed to subtrees should preserve +// their numeric type so that Script expressions can do arithmetic. +TEST(SubTree, LiteralNumericPortsPreserveType) +{ + // clang-format off + static const char* xml_text = R"( + + + + + + + + + + + + + + + + + +)"; + // clang-format on + + BehaviorTreeFactory factory; + + auto tree = factory.createTreeFromText(xml_text); + + // Set the remapped parent value as an integer + tree.rootBlackboard()->set("from_parent", 100); + + const auto status = tree.tickWhileRunning(); + ASSERT_EQ(status, NodeStatus::SUCCESS); +}