Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
94e7300
init
InvincibleRMC Jul 29, 2025
bc24a86
initial implementation
InvincibleRMC Jul 29, 2025
dbd9c05
explicit nullopt default
InvincibleRMC Jul 29, 2025
952690f
switch default to zero_value
InvincibleRMC Jul 30, 2025
33b34df
remove trait cleverness
InvincibleRMC Aug 1, 2025
9074fa6
simplify traits
InvincibleRMC Aug 1, 2025
f267742
Add optional parsing
InvincibleRMC Aug 1, 2025
df0ac5b
Update rosidl_adapter/rosidl_adapter/parser.py
InvincibleRMC Aug 7, 2025
8828933
remove todo
InvincibleRMC Aug 8, 2025
8ff0d40
Merge remote-tracking branch 'origin/rolling' into optional-parsing
InvincibleRMC Aug 12, 2025
f0cecd2
Merge branch 'optional-parsing' into idl-optional
InvincibleRMC Aug 12, 2025
d596f97
init
InvincibleRMC Jul 29, 2025
6db6c3f
initial implementation
InvincibleRMC Jul 29, 2025
6203039
explicit nullopt default
InvincibleRMC Jul 29, 2025
288cc1b
switch default to zero_value
InvincibleRMC Jul 30, 2025
66bff3c
remove trait cleverness
InvincibleRMC Aug 1, 2025
3b4cd0e
simplify traits
InvincibleRMC Aug 1, 2025
789c750
Changelog.
clalancette Jul 29, 2025
87fa804
5.0.1
clalancette Jul 29, 2025
5d65e3d
Add optional parsing
InvincibleRMC Aug 1, 2025
3969e3a
Update rosidl_adapter/rosidl_adapter/parser.py
InvincibleRMC Aug 7, 2025
bfa7f0e
remove todo
InvincibleRMC Aug 8, 2025
4e6d61c
Merge branch 'idl-optional' of github.com:InvincibleRMC/rosidl into i…
InvincibleRMC Aug 12, 2025
c560312
Merge branch 'rolling' into idl-optional
InvincibleRMC Aug 23, 2025
1724ebe
switch to msg file
InvincibleRMC Aug 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rosidl_generator_cpp/resource/idl__struct.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ include_directives = set()
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>

Expand Down
5 changes: 3 additions & 2 deletions rosidl_generator_cpp/resource/msg__struct.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from rosidl_generator_cpp import create_init_alloc_and_member_lists
from rosidl_generator_cpp import escape_string
from rosidl_generator_cpp import escape_wstring
from rosidl_generator_cpp import msg_type_to_cpp
from rosidl_generator_cpp import member_to_cpp
from rosidl_generator_cpp import MSG_TYPE_TO_CPP
from rosidl_generator_cpp import generate_zero_string
from rosidl_generator_cpp import generate_default_string
Expand Down Expand Up @@ -207,15 +208,15 @@ non_defaulted_zero_initialized_members = [
// field types and members
@[for member in message.structure.members]@
using _@(member.name)_type =
@(msg_type_to_cpp(member.type));
@(member_to_cpp(member));
_@(member.name)_type @(member.name);
@[end for]@

@[if len(message.structure.members) != 1 or message.structure.members[0].name != EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@
// setters for named parameter idiom
@[ for member in message.structure.members]@
Type & set__@(member.name)(
const @(msg_type_to_cpp(member.type)) & _arg)
const @(member_to_cpp(member)) & _arg)
{
this->@(member.name) = _arg;
return *this;
Expand Down
57 changes: 40 additions & 17 deletions rosidl_generator_cpp/resource/msg__traits.hpp.em
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@# Included from rosidl_generator_cpp/resource/idl__traits.hpp.em
@{
from rosidl_generator_cpp import msg_type_to_cpp
from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX
from rosidl_parser.definition import ACTION_GOAL_SUFFIX
from rosidl_parser.definition import ACTION_RESULT_SUFFIX
Expand All @@ -13,6 +14,7 @@ from rosidl_parser.definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME
from rosidl_parser.definition import NamespacedType
from rosidl_parser.definition import AbstractSequence
from rosidl_parser.definition import UnboundedSequence
from rosidl_parser.definition import OPTIONAL_ANNOTATION

message_namespace = '::'.join(message.structure.namespaced_type.namespaces)
message_typename = '::'.join(message.structure.namespaced_type.namespaced_name())
Expand Down Expand Up @@ -90,27 +92,39 @@ inline void to_flow_style_yaml(

@[ end if]@
// member: @(member.name)
{
do {
@[ if member.has_annotation(OPTIONAL_ANNOTATION)]@
auto value = msg.@(member.name).value();
if (msg.@(member.name) == std::nullopt) {
out << "@(member.name): null";
@[ if i < len(message.structure.members) - 1]@
out << ", ";
@[ end if]@
break;
}
@[ else]@
auto value = msg.@(member.name);
@[ end if]@
@[ if isinstance(member.type, BasicType)]@
out << "@(member.name): ";
@[ if member.type.typename in ('octet', 'char', 'wchar')]@
rosidl_generator_traits::character_value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::character_value_to_yaml(value, out);
@[ else]@
rosidl_generator_traits::value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::value_to_yaml(value, out);
@[ end if]@
@[ elif isinstance(member.type, AbstractGenericString)]@
out << "@(member.name): ";
rosidl_generator_traits::value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::value_to_yaml(value, out);
@[ elif isinstance(member.type, NamespacedType)]@
out << "@(member.name): ";
to_flow_style_yaml(msg.@(member.name), out);
to_flow_style_yaml(value, out);
@[ elif isinstance(member.type, (AbstractSequence, Array))]@
if (msg.@(member.name).size() == 0) {
if (value.size() == 0) {
out << "@(member.name): []";
} else {
out << "@(member.name): [";
size_t pending_items = msg.@(member.name).size();
for (auto item : msg.@(member.name)) {
size_t pending_items = value.size();
for (auto item : value) {
@[ if isinstance(member.type.value_type, BasicType)]@
@[ if member.type.value_type.typename in ('octet', 'char', 'wchar')]@
rosidl_generator_traits::character_value_to_yaml(item, out);
Expand All @@ -132,7 +146,7 @@ inline void to_flow_style_yaml(
@[ if i < len(message.structure.members) - 1]@
out << ", ";
@[ end if]@
}
} while (false);
@[ end for]@
out << "}";
@[end if]@
Expand All @@ -152,31 +166,40 @@ inline void to_block_style_yaml(

@[ end if]@
// member: @(member.name)
{
do {
if (indentation > 0) {
out << std::string(indentation, ' ');
}
@[ if member.has_annotation(OPTIONAL_ANNOTATION)]@
auto value = msg.@(member.name).value();
if (msg.@(member.name) == std::nullopt) {
out << "@(member.name): null\n";
break;
}
@[ else]@
auto value = msg.@(member.name);
@[ end if]@
@[ if isinstance(member.type, BasicType)]@
out << "@(member.name): ";
@[ if member.type.typename in ('octet', 'char', 'wchar')]@
rosidl_generator_traits::character_value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::character_value_to_yaml(value, out);
@[ else]@
rosidl_generator_traits::value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::value_to_yaml(value, out);
@[ end if]@
out << "\n";
@[ elif isinstance(member.type, AbstractGenericString)]@
out << "@(member.name): ";
rosidl_generator_traits::value_to_yaml(msg.@(member.name), out);
rosidl_generator_traits::value_to_yaml(value, out);
out << "\n";
@[ elif isinstance(member.type, NamespacedType)]@
out << "@(member.name):\n";
to_block_style_yaml(msg.@(member.name), out, indentation + 2);
to_block_style_yaml(value, out, indentation + 2);
@[ elif isinstance(member.type, (AbstractSequence, Array))]@
if (msg.@(member.name).size() == 0) {
if (value.size() == 0) {
out << "@(member.name): []\n";
} else {
out << "@(member.name):\n";
for (auto item : msg.@(member.name)) {
for (auto item : value) {
if (indentation > 0) {
out << std::string(indentation, ' ');
}
Expand All @@ -199,7 +222,7 @@ inline void to_block_style_yaml(
}
}
@[ end if]@
}
} while(false);
@[ end for]@
@[end if]@
} // NOLINT(readability/fn_size)
Expand Down
60 changes: 41 additions & 19 deletions rosidl_generator_cpp/rosidl_generator_cpp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@

from ast import literal_eval
from typing import List
from typing import Optional
from typing import Union

from rosidl_parser.definition import AbstractGenericString
from rosidl_parser.definition import AbstractNestedType
from rosidl_parser.definition import AbstractSequence
from rosidl_parser.definition import AbstractString
from rosidl_parser.definition import AbstractType
from rosidl_parser.definition import AbstractWString
from rosidl_parser.definition import Array
from rosidl_parser.definition import BasicType
from rosidl_parser.definition import BoundedSequence
from rosidl_parser.definition import FLOATING_POINT_TYPES
from rosidl_parser.definition import Member
from rosidl_parser.definition import Message
from rosidl_parser.definition import NamespacedType
from rosidl_parser.definition import OPTIONAL_ANNOTATION
from rosidl_parser.definition import UnboundedSequence
from rosidl_pycommon import generate_files

Expand Down Expand Up @@ -76,7 +82,7 @@ def prefix_with_bom_if_necessary(content: str) -> str:
}


def msg_type_only_to_cpp(type_):
def msg_type_only_to_cpp(type_: AbstractType) -> str:
"""
Convert a message type into the C++ declaration, ignoring array types.

Expand All @@ -103,7 +109,7 @@ def msg_type_only_to_cpp(type_):
return cpp_type


def msg_type_to_cpp(type_):
def msg_type_to_cpp(type_: AbstractType) -> str:
"""
Convert a message type into the C++ declaration, along with the array type.

Expand Down Expand Up @@ -134,7 +140,16 @@ def msg_type_to_cpp(type_):
return cpp_type


def value_to_cpp(type_, value):
def member_to_cpp(member: Member) -> str:
"""Convert a member into the C++ declaration. This exists to add optional support."""
cpp_type = msg_type_to_cpp(member.type)

if member.has_annotation(OPTIONAL_ANNOTATION):
return f'std::optional<{cpp_type}>'
return cpp_type


def value_to_cpp(type_: AbstractType, value) -> str:
"""
Convert a python value into a string representing that value in C++.

Expand All @@ -149,7 +164,9 @@ def value_to_cpp(type_, value):
"""
assert not isinstance(type_, NamespacedType), \
"Could not convert non-primitive type '%s' to CPP" % (type_)
assert value is not None, "Value for type '%s' must not be None" % (type_)

if value is None:
return 'std::nullopt'

if not isinstance(type_, AbstractNestedType):
return primitive_value_to_cpp(type_, value)
Expand All @@ -171,7 +188,7 @@ def value_to_cpp(type_, value):
return cpp_value


def primitive_value_to_cpp(type_, value):
def primitive_value_to_cpp(type_: Union[BasicType, AbstractGenericString], value) -> str:
"""
Convert a python value into a string representing that value in C++.

Expand Down Expand Up @@ -246,30 +263,30 @@ def default_value_from_type(type_):
return 0


def escape_string(s):
def escape_string(s: str) -> str:
s = s.replace('\\', '\\\\')
s = s.replace('"', '\\"')
return s


def escape_wstring(s):
def escape_wstring(s: str) -> str:
return escape_string(s)


def create_init_alloc_and_member_lists(message):
def create_init_alloc_and_member_lists(message: Message):
# A Member object represents the information we need to know to initialize
# a single member of the class.
class Member:

def __init__(self, name):
def __init__(self, name: str):
self.name = name
self.default_value = None
self.zero_value = None
self.default_value: Optional[str] = None
self.zero_value: Optional[str] = None
self.zero_need_array_override = False
self.type = None
self.type: Optional[AbstractType] = None
self.num_prealloc = 0

def same_default_and_zero_value(self, other):
def same_default_and_zero_value(self, other) -> bool:
return self.default_value == other.default_value and \
self.zero_value == other.zero_value

Expand All @@ -280,10 +297,10 @@ def same_default_and_zero_value(self, other):
# value).
class CommonMemberSet:

def __init__(self):
self.members = []
def __init__(self) -> None:
self.members: list[Member] = []

def add_member(self, member):
def add_member(self, member: Member) -> bool:
if not self.members or self.members[-1].same_default_and_zero_value(member):
self.members.append(member)
return True
Expand All @@ -296,12 +313,13 @@ def add_member(self, member):
# initializion in the allocator constructor
# member_list - The list of members that we will generate initialization code
# for in the body of the constructors
init_list = []
alloc_list = []
member_list = []
init_list: list[str] = []
alloc_list: list[str] = []
member_list: list[CommonMemberSet] = []
for field in message.structure.members:
member = Member(field.name)
member.type = field.type

if isinstance(field.type, Array):
alloc_list.append(field.name + '(_alloc)')
if isinstance(field.type.value_type, BasicType) or \
Expand Down Expand Up @@ -346,6 +364,10 @@ def add_member(self, member):
commonset.add_member(member)
member_list.append(commonset)

# Override zero_value if optional
if field.has_annotation(OPTIONAL_ANNOTATION):
member.zero_value = value_to_cpp(field.type, None)

return init_list, alloc_list, member_list


Expand Down
1 change: 1 addition & 0 deletions rosidl_generator_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ if(BUILD_TESTING)
${test_interface_files_MSG_FILES}
${test_interface_files_SRV_FILES}
msg/BasicIdl.idl
msg/Optional.msg
msg/SmallConstant.msg
ADD_LINTER_TESTS
SKIP_INSTALL
Expand Down
11 changes: 11 additions & 0 deletions rosidl_generator_tests/msg/Optional.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@optional
float32 optional_float

@optional
float32 default_optional_float 32.0

# @optional
# string optional_string

# @optional
# short optional_short_array[3]
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

#include <gtest/gtest.h>

#include <optional>

#include "rosidl_generator_tests/msg/detail/arrays__builder.hpp"
#include "rosidl_generator_tests/msg/detail/basic_types__builder.hpp"
#include "rosidl_generator_tests/msg/detail/empty__builder.hpp"
#include "rosidl_generator_tests/msg/detail/multi_nested__builder.hpp"
#include "rosidl_generator_tests/msg/detail/nested__builder.hpp"
#include "rosidl_generator_tests/msg/detail/optional_idl__builder.hpp"

TEST(Test_msg_initialization, build) {
::rosidl_generator_tests::msg::BasicTypes basic =
Expand Down Expand Up @@ -200,4 +203,16 @@ TEST(Test_msg_initialization, build) {
rosidl_generator_tests::msg::Empty empty =
rosidl_generator_tests::build<rosidl_generator_tests::msg::Empty>();
ASSERT_EQ(0, empty.structure_needs_at_least_one_member);

rosidl_generator_tests::msg::OptionalIdl optional =
rosidl_generator_tests::build<rosidl_generator_tests::msg::OptionalIdl>()
.optional_float(0.1f)
.default_optional_float(2.0);
// .optional_short_array({{-100, -200, -100}});
// .optional_string("init");

ASSERT_EQ(0.1f, optional.optional_float);
ASSERT_EQ(2.0, optional.default_optional_float);
// ASSERT_EQ({-100, -200, -100}, optional.default_optional_float);
// ASSERT_EQ("init", optional.optional_string)
}
Loading