diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 173002438ad..8541e3b6a5f 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -642,7 +642,15 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": MockObj(f"reinterpret_cast<{pointer_type} *>({storage_name})"), ) ) - placement_new = CallExpression(f"new({id_.id}) {actual_type}", *call_expr.args) + # `new(p) T()` is value initialization: with a defaulted default ctor it + # zero-fills the object at every site before the ctor runs. The storage + # is .bss and already zero, so emit `new(p) T` when there are no args. + if call_expr.args.args: + placement_new = CallExpression( + f"new({id_.id}) {actual_type}", *call_expr.args + ) + else: + placement_new = RawExpression(f"new({id_.id}) {actual_type}") CORE.add(ExpressionStatement(placement_new)) else: decl = VariableDeclarationExpression(id_.type, "*", id_, static=True) diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index f8881a832ce..962d54c84ca 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -14,7 +14,7 @@ def test_button_is_setup(generate_main): # Then assert "static wake_on_lan::WakeOnLanButton *const" in main_cpp - assert ") wake_on_lan::WakeOnLanButton();" in main_cpp + assert ") wake_on_lan::WakeOnLanButton;" in main_cpp assert "App.register_button" in main_cpp assert "App.register_component" in main_cpp diff --git a/tests/component_tests/deep_sleep/test_deep_sleep.py b/tests/component_tests/deep_sleep/test_deep_sleep.py index e68b1d17cc1..318b214fce7 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep.py +++ b/tests/component_tests/deep_sleep/test_deep_sleep.py @@ -19,7 +19,7 @@ def test_deep_sleep_setup(generate_main): "static deep_sleep::DeepSleepComponent *const deepsleep = reinterpret_cast(deep_sleep__deepsleep__pstorage);" in main_cpp ) - assert "new(deepsleep) deep_sleep::DeepSleepComponent();" in main_cpp + assert "new(deepsleep) deep_sleep::DeepSleepComponent;" in main_cpp assert "App.register_component_(deepsleep, " in main_cpp @@ -49,7 +49,7 @@ def test_deep_sleep_on_wake_trigger(generate_main): """ main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep3.yaml") - assert "deep_sleep::WakeTrigger();" in main_cpp + assert "deep_sleep::WakeTrigger;" in main_cpp assert "Automation" in main_cpp diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor.py b/tests/component_tests/gpio/test_gpio_binary_sensor.py index 60494d9cba4..db7927177a3 100644 --- a/tests/component_tests/gpio/test_gpio_binary_sensor.py +++ b/tests/component_tests/gpio/test_gpio_binary_sensor.py @@ -22,7 +22,7 @@ def test_gpio_binary_sensor_basic_setup( main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml") assert "static gpio::GPIOBinarySensor *const" in main_cpp - assert ") gpio::GPIOBinarySensor();" in main_cpp + assert ") gpio::GPIOBinarySensor;" in main_cpp assert "App.register_binary_sensor" in main_cpp # set_use_interrupt(true) should NOT be generated (uses C++ default) assert "bs_gpio->set_use_interrupt(true);" not in main_cpp diff --git a/tests/component_tests/ili9xxx/test_ili9xxx.py b/tests/component_tests/ili9xxx/test_ili9xxx.py index 3919eb3823e..aead827a5a3 100644 --- a/tests/component_tests/ili9xxx/test_ili9xxx.py +++ b/tests/component_tests/ili9xxx/test_ili9xxx.py @@ -26,6 +26,6 @@ def test_ili9xxx_placement_new_uses_model_subclass( # Pointer is declared as the base type for polymorphism. assert "static ili9xxx::ILI9XXXDisplay *const tft_display" in main_cpp # Placement new runs the subclass constructor — this is the actual regression fix. - assert "new(tft_display) ili9xxx::ILI9XXXST7789V()" in main_cpp + assert "new(tft_display) ili9xxx::ILI9XXXST7789V;" in main_cpp # Base-class default constructor must NOT be used. - assert "new(tft_display) ili9xxx::ILI9XXXDisplay()" not in main_cpp + assert "new(tft_display) ili9xxx::ILI9XXXDisplay;" not in main_cpp diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index f29883684c8..da1636586c2 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -358,7 +358,7 @@ def test_native_generation( main_cpp = generate_main(component_fixture_path("native.yaml")) assert ( - "mipi_spi::MipiSpiBuffer()" + "mipi_spi::MipiSpiBuffer;" in main_cpp ) # A 10ms post-reset delay ({10, 255}) is prepended ahead of the model commands. @@ -375,7 +375,7 @@ def test_lvgl_generation( main_cpp = generate_main(component_fixture_path("lvgl.yaml")) assert ( - "mipi_spi::MipiSpi();" + "mipi_spi::MipiSpi;" in main_cpp ) # A 10ms post-reset delay ({10, 255}) is prepended ahead of the model commands. diff --git a/tests/component_tests/mipi_spi/test_padding_and_offsets.py b/tests/component_tests/mipi_spi/test_padding_and_offsets.py index b2b421c1e20..fd329d2434b 100644 --- a/tests/component_tests/mipi_spi/test_padding_and_offsets.py +++ b/tests/component_tests/mipi_spi/test_padding_and_offsets.py @@ -314,7 +314,7 @@ class TestTemplateParameterPassing: # FRACTION, ROUNDING) # The instantiation should include padding values (0, 0 for default) assert ( - "mipi_spi::MipiSpiBuffer()" + "mipi_spi::MipiSpiBuffer;" in main_cpp ), ( "Padding parameters (0, 0) should be in the MipiSpiBuffer template instantiation" diff --git a/tests/component_tests/template/test_template_text.py b/tests/component_tests/template/test_template_text.py index 2ce9a88d671..8fc62bf5a07 100644 --- a/tests/component_tests/template/test_template_text.py +++ b/tests/component_tests/template/test_template_text.py @@ -30,10 +30,10 @@ def test_template_text_saver_uses_placement_new_with_templated_subclass( in main_cpp ) # Placement new runs the templated subclass constructor. - assert "new(test_text_restore_value_saver) template_::TextSaver<10>()" in main_cpp + assert "new(test_text_restore_value_saver) template_::TextSaver<10>;" in main_cpp # Base-class default ctor must NOT be used. assert ( - "new(test_text_restore_value_saver) template_::TemplateTextSaverBase()" + "new(test_text_restore_value_saver) template_::TemplateTextSaverBase;" not in main_cpp ) # No heap `new TextSaver<...>()` left over — the pre-fix pattern. diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index f5ac07c1cd1..d95cfd48da5 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -14,7 +14,7 @@ def test_text_is_setup(generate_main): # Then assert "static template_::TemplateText *const" in main_cpp - assert ") template_::TemplateText();" in main_cpp + assert ") template_::TemplateText;" in main_cpp assert "App.register_text" in main_cpp diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index eb25af3095f..5047d2c6889 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -14,7 +14,7 @@ def test_text_sensor_is_setup(generate_main): # Then assert "static template_::TemplateTextSensor *const" in main_cpp - assert ") template_::TemplateTextSensor();" in main_cpp + assert ") template_::TemplateTextSensor;" in main_cpp assert "App.register_text_sensor" in main_cpp diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 052513ce979..fb98ad08b53 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -790,3 +790,29 @@ async def test_templatable__lambda_with_std_string() -> None: result = await cg.templatable(lambda_obj, [], ct.std_string) assert isinstance(result, cg.LambdaExpression) + + +class TestPvariablePlacementNew: + def _placement_new(self) -> str: + from esphome.core import CORE + + return next( + str(stmt) for stmt in CORE.main_statements if str(stmt).startswith("new(") + ) + + def test_no_args_uses_default_initialization(self) -> None: + """`new(p) T` rather than `new(p) T()`: value initialization would zero-fill + storage that is already zero, costing a loop of flash at every site.""" + id_ = cg.ID("my_id", is_declaration=True, type=ct.esphome_ns.class_("Foo")) + cg.new_Pvariable(id_) + assert self._placement_new() == "new(my_id) Foo;" + + def test_with_args_calls_constructor(self) -> None: + id_ = cg.ID("my_id", is_declaration=True, type=ct.esphome_ns.class_("Foo")) + cg.new_Pvariable(id_, 1, 2) + assert self._placement_new() == "new(my_id) Foo(1, 2);" + + def test_template_args_without_ctor_args(self) -> None: + id_ = cg.ID("my_id", is_declaration=True, type=ct.esphome_ns.class_("Foo")) + cg.new_Pvariable(id_, cg.TemplateArguments(ct.uint8)) + assert self._placement_new() == "new(my_id) Foo;"