From a325df98da390482e2366f74c3478affd2abea05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2026 21:59:54 -1000 Subject: [PATCH 1/2] [globals] Emit globals.set value lambda with declared global type Pass the global's declared C++ type as the lambda return type so TemplatableFn stores a direct function pointer instead of hitting the deprecated converting trampoline when the value expression deduces to a different type (e.g. an int literal or int-returning lambda assigned to a float global). --- esphome/components/globals/__init__.py | 25 +++++++++++++++++++++++-- tests/components/globals/common.yaml | 8 ++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index ec6730a41c..342b5dace0 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass, field import hashlib from esphome import automation, codegen as cg, config_validation as cv @@ -8,10 +9,11 @@ from esphome.const import ( CONF_TYPE, CONF_VALUE, ) -from esphome.core import CoroPriority, coroutine_with_priority +from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "globals" globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_( @@ -60,11 +62,25 @@ MULTI_CONF = True CONFIG_SCHEMA = _globals_schema +@dataclass +class GlobalsData: + # Map of global id -> user-declared C++ type string (e.g. 'float') so the + # globals.set action can emit lambdas with the matching return type. + types: dict = field(default_factory=dict) + + +def _get_data() -> GlobalsData: + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = GlobalsData() + return CORE.data[DOMAIN] + + # Run with low priority so that namespaces are registered first @coroutine_with_priority(CoroPriority.LATE) async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) restore = config[CONF_RESTORE_VALUE] + _get_data().types[config[CONF_ID]] = config[CONF_TYPE] # Special casing the strings to their own class with a different save/restore mechanism if str(type_) == "std::string" and restore: @@ -108,8 +124,13 @@ async def globals_set_to_code(config, action_id, template_arg, args): full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID]) template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) + # Use the global's declared C++ type as the lambda return type so + # TemplatableFn stores a direct function pointer instead of going through + # the deprecated converting trampoline when the value expression deduces + # to a different type (e.g. int literal assigned to a float global). + value_type = cg.RawExpression(_get_data().types[config[CONF_ID]]) templ = await cg.templatable( - config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True + config[CONF_VALUE], args, value_type, to_exp=cg.RawExpression ) cg.add(var.set_value(templ)) return var diff --git a/tests/components/globals/common.yaml b/tests/components/globals/common.yaml index 35dca0624f..6d5721d3be 100644 --- a/tests/components/globals/common.yaml +++ b/tests/components/globals/common.yaml @@ -4,6 +4,14 @@ esphome: - globals.set: id: glob_int value: "10" + # Set a float global with an integer literal - must emit the correct + # return type so TemplatableFn stores a direct function pointer. + - globals.set: + id: glob_float + value: "102" + - globals.set: + id: glob_float + value: !lambda "return 42;" globals: - id: glob_int From 503e299d15efac8cd9d44b899b294234d72dc10d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2026 22:10:24 -1000 Subject: [PATCH 2/2] [globals] Use value_type alias instead of stashing type in CORE.data --- esphome/components/globals/__init__.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 342b5dace0..46725fe6dd 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -1,4 +1,3 @@ -from dataclasses import dataclass, field import hashlib from esphome import automation, codegen as cg, config_validation as cv @@ -9,11 +8,10 @@ from esphome.const import ( CONF_TYPE, CONF_VALUE, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] -DOMAIN = "globals" globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_( @@ -62,25 +60,11 @@ MULTI_CONF = True CONFIG_SCHEMA = _globals_schema -@dataclass -class GlobalsData: - # Map of global id -> user-declared C++ type string (e.g. 'float') so the - # globals.set action can emit lambdas with the matching return type. - types: dict = field(default_factory=dict) - - -def _get_data() -> GlobalsData: - if DOMAIN not in CORE.data: - CORE.data[DOMAIN] = GlobalsData() - return CORE.data[DOMAIN] - - # Run with low priority so that namespaces are registered first @coroutine_with_priority(CoroPriority.LATE) async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) restore = config[CONF_RESTORE_VALUE] - _get_data().types[config[CONF_ID]] = config[CONF_TYPE] # Special casing the strings to their own class with a different save/restore mechanism if str(type_) == "std::string" and restore: @@ -124,11 +108,11 @@ async def globals_set_to_code(config, action_id, template_arg, args): full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID]) template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) - # Use the global's declared C++ type as the lambda return type so + # Use the global's value_type alias as the lambda return type so # TemplatableFn stores a direct function pointer instead of going through # the deprecated converting trampoline when the value expression deduces # to a different type (e.g. int literal assigned to a float global). - value_type = cg.RawExpression(_get_data().types[config[CONF_ID]]) + value_type = cg.RawExpression(f"{full_id.type}::value_type") templ = await cg.templatable( config[CONF_VALUE], args, value_type, to_exp=cg.RawExpression )