From 10ea8c34921b27687da6e5f806c2f6b60ca65e17 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 May 2026 17:29:25 -0500 Subject: [PATCH] [light] Normalize trigger args to const remove_cvref_t & --- esphome/components/light/automation.h | 15 +++++++-------- esphome/components/light/automation.py | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 3bd0d5afbd7..993d4a2ea66 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -37,16 +37,15 @@ template class ToggleAction : public A // Trigger args are forwarded to the apply function so user lambdas // (e.g. `brightness: !lambda "return x;"`) keep working. // -// Trigger args are forwarded as `Ts...`. The previous `const Ts &...` -// form caused codegen to emit `const T &` for each arg in the apply -// lambda's parameter list, which is invalid C++ source text when T is -// already a reference (e.g. `const std::string & &` for triggers that -// pass `std::string &`). Forwarding `Ts...` lets the codegen reuse the -// trigger's `args` types unchanged for both the apply lambda and any -// inner field lambdas, so they always type-match. +// Trigger args are normalized to `const std::remove_cvref_t &...` so +// the codegen can emit a matching parameter list for both the apply lambda +// and any inner field lambdas without producing invalid C++ source text +// (e.g. `const T & &` if Ts already carries a reference, or `const const +// T &` if Ts already carries a const). This keeps trigger args no-copy +// regardless of whether the trigger supplies `T`, `T &`, or `const T &`. template class LightControlAction : public Action { public: - using ApplyFn = void (*)(LightState *, LightCall &, Ts...); + using ApplyFn = void (*)(LightState *, LightCall &, const std::remove_cvref_t &...); LightControlAction(LightState *parent, ApplyFn apply) : parent_(parent), apply_(apply) {} void play(const Ts &...x) override { diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index c796b97db06..cef774af38d 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -200,6 +200,15 @@ async def light_control_to_code(config, action_id, template_arg, args): (CONF_WARM_WHITE, "set_warm_white", cg.float_), ) + # Normalize trigger args to `const std::remove_cvref_t &` so the + # apply lambda and any inner field lambdas (generated below via + # `process_lambda`) share one parameter spelling that's well-formed for + # any T (value, ref, or const-ref). Matches LightControlAction::ApplyFn. + normalized_args = [ + (cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &"), n) + for t, n in args + ] + fwd_args = ", ".join(name for _, name in args) body_lines: list[str] = [] @@ -208,7 +217,7 @@ async def light_control_to_code(config, action_id, template_arg, args): continue value = config[conf_key] if isinstance(value, Lambda): - inner = await cg.process_lambda(value, args, return_type=type_) + inner = await cg.process_lambda(value, normalized_args, return_type=type_) body_lines.append(f"call.{setter}(({inner})({fwd_args}));") else: body_lines.append(f"call.{setter}({cg.safe_exp(value)});") @@ -216,7 +225,7 @@ async def light_control_to_code(config, action_id, template_arg, args): if CONF_EFFECT in config: if isinstance(config[CONF_EFFECT], Lambda): inner_lambda = await cg.process_lambda( - config[CONF_EFFECT], args, return_type=cg.std_string + config[CONF_EFFECT], normalized_args, return_type=cg.std_string ) body_lines.append( f"{{ auto __effect_s = ({inner_lambda})({fwd_args});\n" @@ -230,13 +239,10 @@ async def light_control_to_code(config, action_id, template_arg, args): f"call.set_effect(static_cast({_resolve_effect_index(config)}));" ) - # Match LightControlAction::ApplyFn signature: forward trigger args as Ts... - # so the apply lambda's parameter types match both ApplyFn and the - # inner field lambdas (which are generated from `args` directly). apply_args = [ (LightState.operator("ptr"), "parent"), (LightCall.operator("ref"), "call"), - *args, + *normalized_args, ] apply_lambda = LambdaExpression( ["\n".join(body_lines)],