Skip to content

Commit ca9c88b

Browse files
fix: Fix nested parameter generation within __map (#12)
* fix: fix nested parameters within a __map * chore: lint * fix: revert to existing solution
1 parent 15f9af7 commit ca9c88b

File tree

2 files changed

+136
-5
lines changed

2 files changed

+136
-5
lines changed

generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ def int_to_integer_str(value: str):
101101

102102
def get_dynamic_parameter_field(yaml_parameter_name: str):
103103
tmp = yaml_parameter_name.split('.')
104-
parameter_field = tmp[-1]
105-
return parameter_field
104+
num_nested = [i for i, val in enumerate(tmp) if is_mapped_parameter(val)]
105+
field = tmp[(max(num_nested) + 1) :] if len(num_nested) else [tmp[-1]]
106+
return '.'.join(field)
106107

107108

108109
def get_dynamic_mapped_parameter(yaml_parameter_name: str):
@@ -115,8 +116,8 @@ def get_dynamic_mapped_parameter(yaml_parameter_name: str):
115116

116117
def get_dynamic_struct_name(yaml_parameter_name: str):
117118
tmp = yaml_parameter_name.split('.')
118-
num_nested = sum([is_mapped_parameter(val) for val in tmp])
119-
struct_name = tmp[: -(num_nested + 1)]
119+
num_nested = [i for i, val in enumerate(tmp) if is_mapped_parameter(val)]
120+
struct_name = tmp[: (min(num_nested))] if len(num_nested) else []
120121
return '.'.join(struct_name)
121122

122123

@@ -806,7 +807,7 @@ def parse_params(self, name, value, nested_name_list):
806807
var = VariableDeclaration(code_gen_variable)
807808

808809
# check if runtime parameter
809-
is_runtime_parameter = is_mapped_parameter(self.struct_tree.struct_name)
810+
is_runtime_parameter = is_mapped_parameter(param_name)
810811

811812
if is_runtime_parameter:
812813
declare_parameter_set = SetRuntimeParameter(param_name, code_gen_variable)

generate_parameter_library_py/generate_parameter_library_py/test/test_nested_map_fix.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,133 @@ def test_single_map_parameter_names():
156156
# Clean up temporary files
157157
os.unlink(yaml_file.name)
158158
os.unlink(output_file.name)
159+
160+
161+
def test_control_modes_nested_structures():
162+
"""Test nested structures within mapped parameters.
163+
164+
This tests the specific issue where parameters nested within mapped structures
165+
(like control_modes.__map_control_mode_ids.fixed_heading.enabled) were not
166+
generating loop iteration code correctly.
167+
"""
168+
169+
control_modes_yaml_content = """autopilot_params:
170+
active_control_mode:
171+
type: string
172+
default_value: "dynamic_heading"
173+
description: "The current active control mode"
174+
175+
control_mode_ids:
176+
type: string_array
177+
default_value: ["dynamic_heading", "fixed_heading"]
178+
description: "List of available control modes"
179+
180+
control_modes:
181+
__map_control_mode_ids:
182+
use_trajectory:
183+
type: bool
184+
default_value: true
185+
description: "Use generated trajectory as control reference"
186+
187+
fixed_heading:
188+
enabled:
189+
type: bool
190+
default_value: false
191+
description: "Enable fixed heading control"
192+
angle:
193+
type: double
194+
default_value: 0.0
195+
description: "Fixed heading angle in degrees"
196+
validation:
197+
bounds<>: [0.0, 360.0]
198+
"""
199+
200+
with tempfile.NamedTemporaryFile(
201+
mode='w', suffix='.yaml', delete=False
202+
) as yaml_file:
203+
yaml_file.write(control_modes_yaml_content)
204+
yaml_file.flush()
205+
206+
with tempfile.NamedTemporaryFile(
207+
mode='w', suffix='.py', delete=False
208+
) as output_file:
209+
try:
210+
run_python(output_file.name, yaml_file.name, 'test_validate.hpp')
211+
212+
with open(output_file.name, 'r') as f:
213+
generated_code = f.read()
214+
215+
# Check for proper loop generation
216+
assert (
217+
'for value_1 in updated_params.control_mode_ids:' in generated_code
218+
), 'Should generate loop over control_mode_ids'
219+
220+
# Check that we're using get_entry correctly
221+
assert (
222+
'updated_params.control_modes.get_entry(value_1)' in generated_code
223+
or 'entry = updated_params.control_modes.get_entry(value_1)'
224+
in generated_code
225+
), 'Should use get_entry to access mapped parameters'
226+
227+
# Get all parameter name lines
228+
lines = generated_code.split('\n')
229+
param_name_lines = [
230+
line
231+
for line in lines
232+
if 'param_name' in line and 'control_modes' in line
233+
]
234+
235+
# Assert there are parameter name lines
236+
assert (
237+
len(param_name_lines) > 0
238+
), 'Should have found parameter name lines for control_modes'
239+
240+
# Check for expected parameter patterns (using {value_1} for dynamic substitution)
241+
expected_patterns = [
242+
'control_modes.{value_1}.use_trajectory',
243+
'control_modes.{value_1}.fixed_heading.enabled',
244+
'control_modes.{value_1}.fixed_heading.angle',
245+
]
246+
247+
for pattern in expected_patterns:
248+
pattern_found = any(pattern in line for line in param_name_lines)
249+
assert pattern_found, (
250+
f"Expected pattern '{pattern}' not found in generated code.\nParam lines:\n"
251+
+ '\n'.join(param_name_lines[:5])
252+
)
253+
254+
# Ensure no double dots in parameter names
255+
for line in param_name_lines:
256+
assert (
257+
'..' not in line
258+
), f'Found double dots in parameter name: {line.strip()}'
259+
260+
# Check that we're accessing nested parameters via entry, not via __map_
261+
entry_access_lines = [
262+
line
263+
for line in lines
264+
if 'entry.' in line and ('fixed_heading' in line)
265+
]
266+
assert (
267+
len(entry_access_lines) > 0
268+
), 'Should access nested parameters via entry variable'
269+
270+
# Verify no direct access to __map_control_mode_ids in actual code
271+
# (error messages can reference the YAML path, but code should not access __map_ attributes)
272+
wrong_access_lines = [
273+
line
274+
for line in lines
275+
if 'control_modes.__map_control_mode_ids' in line
276+
and not line.strip().startswith('#') # Ignore comments
277+
and 'InvalidParameterValueException'
278+
not in line # Ignore error messages
279+
]
280+
assert len(wrong_access_lines) == 0, (
281+
f'Should not directly access __map_control_mode_ids in code. Found:\n'
282+
+ '\n'.join(wrong_access_lines)
283+
)
284+
285+
finally:
286+
# Clean up temporary files
287+
os.unlink(yaml_file.name)
288+
os.unlink(output_file.name)

0 commit comments

Comments
 (0)