From 96df32024d8d2cca247413fa88637e15cf0e217a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Oct 2026 21:17:19 -0500 Subject: [PATCH] [core] Let shared_progmem_array emit an unshared array Identical lambdas can carry separate static state, so callers need a way to keep such tables apart while still getting a collision free name. --- esphome/cpp_generator.py | 24 ++++++++++++++---------- tests/unit_tests/test_cpp_generator.py | 7 +++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index ee018e9dea1..a5ffaefe0be 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -469,12 +469,13 @@ def progmem_array(id_, rhs) -> "MockObj": def shared_progmem_array( - name: str, type_: "MockObjClass", rhs: SafeExpType + name: str, type_: "MockObjClass", rhs: SafeExpType, *, share: bool = True ) -> "MockObj": """Emit a global PROGMEM array once per distinct type and contents; later calls reuse it. The array is ``static constexpr``, so elements must be constant expressions and lambdas must be captureless. Its name is made unique against every config id and variable. + ``share=False`` always emits a new array, e.g. for lambdas that may keep static state. """ from esphome.config import iter_ids from esphome.config_validation import RESERVED_IDS @@ -482,15 +483,18 @@ def shared_progmem_array( arrays: dict[str, MockObj] = CORE.data.setdefault("shared_progmem_array", {}) rhs = safe_exp(rhs) key = f"{type_} {rhs}" - if (array := arrays.get(key)) is None: - used = {str(i) for i, _ in iter_ids(CORE.config)} - used |= {str(i) for i in CORE.variables} - used |= set(RESERVED_IDS) | CORE.loaded_integrations - id_ = ID(ensure_unique_string(name, used), is_declaration=True, type=type_) - # Global, so any scope can use it; anything a lambda references is already declared. - CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs)) - array = arrays[key] = MockObj(id_, ".") - CORE.register_variable(id_, array) + if share and (array := arrays.get(key)) is not None: + return array + used = {str(i) for i, _ in iter_ids(CORE.config)} + used |= {str(i) for i in CORE.variables} + used |= set(RESERVED_IDS) | CORE.loaded_integrations + id_ = ID(ensure_unique_string(name, used), is_declaration=True, type=type_) + # Global, so any scope can use it; anything a lambda references is already declared. + CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs)) + array = MockObj(id_, ".") + CORE.register_variable(id_, array) + if share: + arrays[key] = array return array diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 0ee6d1ae5ca..06b7c56baeb 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -822,6 +822,13 @@ class TestSharedProgmemArray: assert sum("PROGMEM" in str(st) for st in CORE.global_statements) == 2 assert not any("PROGMEM" in str(st) for st in CORE.main_statements) + def test_share_false_always_emits_a_new_array(self) -> None: + CORE.config = {} + a = cg.shared_progmem_array("table", ct.uint8, [1], share=False) + b = cg.shared_progmem_array("table", ct.uint8, [1], share=False) + assert str(a) != str(b) + assert sum("PROGMEM" in str(st) for st in CORE.global_statements) == 2 + def test_same_contents_different_type_are_separate(self) -> None: CORE.config = {} a = cg.shared_progmem_array("table", ct.uint8, [1])