mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[core] Let a nested repeat shadow the outer iteration variable (#20634)
This commit is contained in:
+16
-1
@@ -906,6 +906,21 @@ async def while_action_to_code(
|
||||
return var
|
||||
|
||||
|
||||
def _shadow_iteration(args: TemplateArgsType) -> TemplateArgsType:
|
||||
"""Rename an inherited `iteration` so the inner repeat's own counter can shadow it."""
|
||||
names = {name for _, name in args}
|
||||
renamed: TemplateArgsType = []
|
||||
for type_, name in args:
|
||||
if name == "iteration":
|
||||
depth = 1
|
||||
while f"iteration_{depth}" in names:
|
||||
depth += 1
|
||||
name = f"iteration_{depth}"
|
||||
names.add(name)
|
||||
renamed.append((type_, name))
|
||||
return renamed
|
||||
|
||||
|
||||
@register_action(
|
||||
"repeat",
|
||||
RepeatAction,
|
||||
@@ -929,7 +944,7 @@ async def repeat_action_to_code(
|
||||
actions = await build_action_list(
|
||||
config[CONF_THEN],
|
||||
cg.TemplateArguments(cg.uint32, *template_arg.args),
|
||||
[(cg.uint32, "iteration"), *args],
|
||||
[(cg.uint32, "iteration"), *_shadow_iteration(args)],
|
||||
)
|
||||
for action in actions:
|
||||
cg.add(var.add_then_action(action))
|
||||
|
||||
@@ -23,6 +23,10 @@ esphome:
|
||||
then:
|
||||
- logger.log: my_script is running
|
||||
|
||||
globals:
|
||||
- id: nested_total
|
||||
type: int
|
||||
|
||||
script:
|
||||
- id: my_script
|
||||
mode: single
|
||||
@@ -71,3 +75,16 @@ script:
|
||||
count: 5
|
||||
then:
|
||||
- logger.log: looping!
|
||||
- repeat:
|
||||
count: 2
|
||||
then:
|
||||
- repeat:
|
||||
count: 2
|
||||
then:
|
||||
- delay: 200ms
|
||||
- lambda: 'ESP_LOGD("main", "inner iteration %u", iteration);'
|
||||
- globals.set:
|
||||
id: nested_total
|
||||
value: !lambda |-
|
||||
int total = iteration;
|
||||
return total;
|
||||
|
||||
@@ -13,9 +13,13 @@ from esphome.automation import (
|
||||
ApplyCondition,
|
||||
ApplyField,
|
||||
CallbackAutomation,
|
||||
LambdaAction,
|
||||
RepeatAction,
|
||||
TriggerForwarder,
|
||||
TriggerOnFalseForwarder,
|
||||
TriggerOnTrueForwarder,
|
||||
_shadow_iteration,
|
||||
build_action,
|
||||
build_callback_automation,
|
||||
build_callback_automations,
|
||||
build_parent_callback_automation,
|
||||
@@ -41,7 +45,13 @@ from esphome.automation import (
|
||||
)
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AUTOMATION_ID, CONF_ID, CONF_THEN
|
||||
from esphome.const import (
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_COUNT,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
CONF_TYPE_ID,
|
||||
)
|
||||
from esphome.core import CORE, ID, KEY_CORE, KEY_TARGET_PLATFORM, EsphomeError, Lambda
|
||||
from esphome.cpp_generator import MockObj, RawExpression
|
||||
from esphome.types import ConfigType
|
||||
@@ -1545,3 +1555,56 @@ async def test_build_callback_automation_forwarder(mock_cg: MockCodegen) -> None
|
||||
assert str(mock_cg.add.call_args.args[0]) == (
|
||||
f"parent->add_cb(TriggerForwarder<bool>{{{NEW_OBJ}}})"
|
||||
)
|
||||
|
||||
|
||||
def test_shadow_iteration_leaves_other_names() -> None:
|
||||
args = [(cg.std_string, "prefix"), (cg.uint32, "iteration_1")]
|
||||
assert _shadow_iteration(args) == args
|
||||
|
||||
|
||||
def test_shadow_iteration_renames_inherited_counter() -> None:
|
||||
assert _shadow_iteration([(cg.uint32, "iteration"), (cg.bool_, "x")]) == [
|
||||
(cg.uint32, "iteration_1"),
|
||||
(cg.bool_, "x"),
|
||||
]
|
||||
|
||||
|
||||
def test_shadow_iteration_picks_first_unused_suffix() -> None:
|
||||
"""A counter keeps its suffix at deeper levels; the new one takes the next free name."""
|
||||
args = [
|
||||
(cg.uint32, "iteration"),
|
||||
(cg.uint32, "iteration_1"),
|
||||
(cg.int_, "iteration_3"),
|
||||
]
|
||||
assert _shadow_iteration(args) == [
|
||||
(cg.uint32, "iteration_2"),
|
||||
(cg.uint32, "iteration_1"),
|
||||
(cg.int_, "iteration_3"),
|
||||
]
|
||||
|
||||
|
||||
def _repeat_conf(id_: str, then: list[ConfigType]) -> ConfigType:
|
||||
return {
|
||||
"repeat": {CONF_COUNT: 2, CONF_THEN: then},
|
||||
CONF_TYPE_ID: ID(id_, is_declaration=True, type=RepeatAction),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_nested_repeat_lambda_parameters_are_distinct() -> None:
|
||||
"""Each repeat level adds its own `iteration`; inherited ones are renamed, not duplicated."""
|
||||
CORE.config = {}
|
||||
log = {
|
||||
"lambda": Lambda('ESP_LOGD("t", "%u", iteration);'),
|
||||
CONF_TYPE_ID: ID("log_action", is_declaration=True, type=LambdaAction),
|
||||
}
|
||||
conf = _repeat_conf(
|
||||
"outer", [_repeat_conf("middle", [_repeat_conf("inner", [log])])]
|
||||
)
|
||||
await build_action(conf, cg.TemplateArguments(cg.uint8), [(cg.uint8, "level")])
|
||||
text = CORE.cpp_main_section
|
||||
assert (
|
||||
"(uint32_t iteration, uint32_t iteration_2, uint32_t iteration_1, uint8_t level)"
|
||||
in text
|
||||
)
|
||||
assert "uint32_t iteration, uint32_t iteration)" not in text
|
||||
|
||||
Reference in New Issue
Block a user