From 9621c5a3d30ea91b879f2ce33f56fd009e2456de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 1 Mar 2026 11:08:23 -1000 Subject: [PATCH] [core] Compile-time detection of loop() overrides Replace the runtime GCC-specific pointer-to-member-function comparison in has_overridden_loop() with a compile-time std::is_same_v check in a templated register_component_(). The template resolves &T::loop vs &Component::loop at compile time and passes the result to register_component_impl_(), which stores it as bit 5 in the existing component_state_ byte (zero additional RAM). This eliminates: - The non-standard -Wpmf-conversions GCC extension - The USE_HOST/CLANG_TIDY special case that incorrectly returned true for all components (putting every component in the loop list) - Runtime vtable probing during calculate_looping_components_() --- esphome/core/application.cpp | 7 ++++++- esphome/core/application.h | 10 +++++++++- esphome/core/component.cpp | 12 ------------ esphome/core/component.h | 7 +++++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c977fd66b3..0e6cec16cf 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -79,7 +79,12 @@ static void insertion_sort_by_priority(Iterator first, Iterator last) { } } -void Application::register_component_(Component *comp) { this->components_.push_back(comp); } +void Application::register_component_impl_(Component *comp, bool has_loop) { + if (has_loop) { + comp->component_state_ |= COMPONENT_HAS_LOOP; + } + this->components_.push_back(comp); +} void Application::setup() { ESP_LOGI(TAG, "Running through setup()"); ESP_LOGV(TAG, "Sorting components by setup priority"); diff --git a/esphome/core/application.h b/esphome/core/application.h index 13e0f63885..4f4848ba21 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "esphome/core/component.h" #include "esphome/core/defines.h" @@ -531,7 +532,14 @@ class Application { #endif #endif - void register_component_(Component *comp); + /// Register a component, detecting loop() override at compile time. + /// The template resolves &T::loop vs &Component::loop as a constexpr bool + /// and forwards it to register_component_impl_ which stores it in component_state_. + template void register_component_(T *comp) { + this->register_component_impl_(comp, !std::is_same_v); + } + + void register_component_impl_(Component *comp, bool has_loop); void calculate_looping_components_(); void add_looping_components_by_state_(bool match_loop_done); diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index fd4e0d2984..1660fa3b32 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -490,18 +490,6 @@ void Component::set_setup_priority(float priority) { } #endif -bool Component::has_overridden_loop() const { -#if defined(USE_HOST) || defined(CLANG_TIDY) - return true; -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpmf-conversions" - bool loop_overridden = (void *) (this->*(&Component::loop)) != (void *) (&Component::loop); -#pragma GCC diagnostic pop - return loop_overridden; -#endif -} - PollingComponent::PollingComponent(uint32_t update_interval) : update_interval_(update_interval) {} void PollingComponent::call_setup() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 6b920da290..d8102ea670 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -76,6 +76,8 @@ inline constexpr uint8_t STATUS_LED_MASK = 0x18; inline constexpr uint8_t STATUS_LED_OK = 0x00; inline constexpr uint8_t STATUS_LED_WARNING = 0x08; inline constexpr uint8_t STATUS_LED_ERROR = 0x10; +// Component loop override flag uses bit 5 (set at registration time) +inline constexpr uint8_t COMPONENT_HAS_LOOP = 0x20; // Remove before 2026.8.0 enum class RetryResult { DONE, RETRY }; @@ -271,7 +273,7 @@ class Component { */ void status_momentary_error(const char *name, uint32_t length = 5000); - bool has_overridden_loop() const; + bool has_overridden_loop() const { return (this->component_state_ & COMPONENT_HAS_LOOP) != 0; } /** Set where this component was loaded from for some debug messages. * @@ -510,7 +512,8 @@ class Component { /// Bits 0-2: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED, 0x04=LOOP_DONE) /// Bit 3: STATUS_LED_WARNING /// Bit 4: STATUS_LED_ERROR - /// Bits 5-7: Unused - reserved for future expansion + /// Bit 5: Has overridden loop() (set at registration time) + /// Bits 6-7: Unused - reserved for future expansion uint8_t component_state_{0x00}; volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context };