From 67f26b6cf77bbcdb4feba5f600b301167ecc8964 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Apr 2026 22:46:31 -1000 Subject: [PATCH] fix 8266 flash string opt --- esphome/components/globals/__init__.py | 2 +- esphome/cpp_generator.py | 7 ++++--- tests/unit_tests/test_cpp_generator.py | 16 +++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index fe83b1ea7c..ec6730a41c 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -109,7 +109,7 @@ async def globals_set_to_code(config, action_id, template_arg, args): template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) templ = await cg.templatable( - config[CONF_VALUE], args, None, to_exp=cg.RawExpression + config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True ) cg.add(var.set_value(templ)) return var diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 814e37e02c..cf90b878e1 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -819,6 +819,8 @@ async def templatable( args: list[tuple[SafeExpType, str]], output_type: SafeExpType | None, to_exp: Callable | dict = None, + *, + wrap_constant: bool = False, ): """Generate code for a templatable config option. @@ -850,9 +852,8 @@ async def templatable( return FlashStringLiteral(value) # Wrap non-string constants in stateless lambdas so that TemplatableFn # (used by TEMPLATABLE_VALUE macro) stores them as function pointers. - # When output_type is None, the lambda omits the return type annotation - # and the C++ compiler deduces it (used by globals where T is unknown). - if output_type is not std_string: + # wrap_constant=True forces wrapping even with output_type=None (compiler deduces type). + if (output_type is not None or wrap_constant) and output_type is not std_string: return LambdaExpression( f"return {safe_exp(value)};", args, diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index c75851df0c..81ae586e23 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -652,11 +652,11 @@ async def test_templatable__empty_string_with_std_string() -> None: @pytest.mark.asyncio async def test_templatable__string_with_none_output_type() -> None: - """Static string with output_type=None returns stateless lambda (no return type).""" + """Static string with output_type=None returns raw string (no wrapping).""" result = await cg.templatable("hello", [], None) - assert isinstance(result, cg.LambdaExpression) - assert result.capture == "" + assert isinstance(result, str) + assert result == "hello" @pytest.mark.asyncio @@ -678,11 +678,10 @@ async def test_templatable__string_with_non_string_output_type() -> None: @pytest.mark.asyncio async def test_templatable__with_to_exp_callable() -> None: - """When to_exp is provided with output_type=None, result is lambda-wrapped.""" + """When to_exp is provided, it is applied to non-template values.""" result = await cg.templatable(42, [], None, to_exp=lambda x: x * 2) - assert isinstance(result, cg.LambdaExpression) - assert result.capture == "" + assert result == 84 @pytest.mark.asyncio @@ -696,12 +695,11 @@ async def test_templatable__with_to_exp_callable_and_output_type() -> None: @pytest.mark.asyncio async def test_templatable__with_to_exp_dict() -> None: - """When to_exp is a dict, value is looked up and lambda-wrapped.""" + """When to_exp is a dict, value is looked up.""" mapping: dict[str, int] = {"on": 1, "off": 0} result = await cg.templatable("on", [], None, to_exp=mapping) - assert isinstance(result, cg.LambdaExpression) - assert result.capture == "" + assert result == 1 @pytest.mark.asyncio