diff --git a/proto2ros/proto2ros/output/interfaces.py b/proto2ros/proto2ros/output/interfaces.py index 9585833..7f2b449 100644 --- a/proto2ros/proto2ros/output/interfaces.py +++ b/proto2ros/proto2ros/output/interfaces.py @@ -41,6 +41,16 @@ def dump_message_specification(message_spec: MessageSpecification) -> str: any_type = Type(str(field.type).replace(BaseType.__str__(field.type), "proto2ros/Any")) field = Field(any_type, field.name) line = str(field) + # ROS IDL does not allow a package to fully qualify its own type + # references: `proto2ros/Value` must be written as `Value` when + # the field appears in a message that itself belongs to `proto2ros`. + # rosidl's type-description generator fails with a KeyError when it + # encounters a self-qualified reference. Strip the prefix here so + # the emitted .msg files conform to the rosidl convention used by + # every other ROS package. + self_pkg = message_spec.base_type.pkg_name + if field.type.pkg_name == self_pkg: + line = line.replace(f"{self_pkg}/", "", 1) if qualifiers: line += " # " + ", ".join(qualifiers) output.append(line) diff --git a/proto2ros/proto2ros/output/test_interfaces.py b/proto2ros/proto2ros/output/test_interfaces.py new file mode 100644 index 0000000..a9fa6f0 --- /dev/null +++ b/proto2ros/proto2ros/output/test_interfaces.py @@ -0,0 +1,44 @@ +# Copyright (c) 2024 Robotics and AI Institute LLC dba RAI Institute. All rights reserved. + +"""Unit tests for proto2ros.output.interfaces.""" + +from rosidl_adapter.parser import Field, MessageSpecification, Type + +from proto2ros.output.interfaces import dump_message_specification + + +def _make_spec(pkg: str, msg: str, fields: list) -> MessageSpecification: + return MessageSpecification(pkg, msg, fields, []) + + +def test_dump_strips_self_package_qualifier() -> None: + """Same-package type references must not be fully qualified in .msg output. + + rosidl's type-description generator fails with KeyError when a package + fully qualifies its own type references (e.g. ``proto2ros/Value`` inside + a ``proto2ros`` package message). The emitted line must use the bare + type name instead. + """ + fields = [Field(Type("proto2ros/Value[]"), "values")] + spec = _make_spec("proto2ros", "List", fields) + output = dump_message_specification(spec) + assert "proto2ros/Value" not in output, ( + "self-package qualifier must be stripped from same-package field types" + ) + assert "Value[] values" in output + + +def test_dump_preserves_cross_package_qualifier() -> None: + """Cross-package type references must keep their fully qualified form.""" + fields = [Field(Type("builtin_interfaces/Time"), "stamp")] + spec = _make_spec("my_msgs", "Header", fields) + output = dump_message_specification(spec) + assert "builtin_interfaces/Time stamp" in output + + +def test_dump_preserves_primitive_fields() -> None: + """Messages with only primitive fields must be unaffected.""" + spec = _make_spec("my_msgs", "Counts", []) + output = dump_message_specification(spec) + assert isinstance(output, str) +