From 140488723d646d380323bff450c933461fc08c08 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 8 Oct 2026 19:55:29 -1000 Subject: [PATCH] Keep get_name() returning a reference off ESP8266 ProgmemStringRef now holds a StringRef, so get_name() and friendly_name_() can keep returning const StringRef & on other platforms. External components that return get_name() from a const StringRef & getter would otherwise return a reference to a temporary. friendly_name_() is deprecated there, not removed. --- esphome/components/mqtt/mqtt_component.cpp | 3 +++ esphome/components/mqtt/mqtt_component.h | 4 ++++ esphome/core/entity_base.h | 4 ++-- esphome/core/string_ref.h | 28 +++++++++++----------- tests/components/core/test_string_ref.cpp | 10 ++++++++ 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 1ce152f3e67..042867d2600 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -413,6 +413,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } // Pull these properties from EntityBase if not overridden +#ifndef USE_ESP8266 +const StringRef &MQTTComponent::friendly_name_() const { return this->get_entity()->get_name_progmem().ram_ref(); } +#endif const LogString *MQTTComponent::log_name_() const { return this->get_entity()->get_log_name(); } StringRef MQTTComponent::get_default_object_id_to_(std::span buf) const { return this->get_entity()->get_object_id_to(buf); diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index f97b60d5eae..bc3288707b6 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -300,6 +300,10 @@ class MQTTComponent : public Component { /// Entity name for a "%s" log argument. const LogString *log_name_() const; +#ifndef USE_ESP8266 + ESPDEPRECATED("Use log_name_() for logging. Removed in 2027.5.0", "2026.11.0") + const StringRef &friendly_name_() const; +#endif /// Get the icon field of this component into a stack buffer const char *get_icon_to_(std::span buf) const { return this->get_entity()->get_icon_to(buf); } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 1105759a21e..06bf9570ad4 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -86,8 +86,8 @@ class EntityBase { } #else ESPDEPRECATED("Use get_name_to() or get_log_name() instead. Will be removed in ESPHome 2027.5.0", "2026.11.0") - StringRef get_name() const { return this->name_.ram_ref(); } - StringRef get_name_to(std::span /*buffer*/) const { return this->name_.ram_ref(); } + const StringRef &get_name() const { return this->name_.ram_ref(); } + const StringRef &get_name_to(std::span /*buffer*/) const { return this->name_.ram_ref(); } #endif bool name_equals(const StringRef &other) const { return this->name_.equals(other); } diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index f618d117833..f9e2e7ed471 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -223,27 +223,28 @@ inline std::string operator+(const std::string &lhs, const StringRef &rhs) { str.append(rhs.c_str(), rhs.size()); return str; } + /// A string that may live in flash (on ESP8266): pointer and length, with no byte-reading operations. /// progmem_ptr() is safe as a "%s" log argument; read the contents with write_to() or equals(). class ProgmemStringRef { public: constexpr ProgmemStringRef() = default; - constexpr ProgmemStringRef(const char *s, size_t len) : base_(s), len_(len) {} - constexpr explicit ProgmemStringRef(const StringRef &s) : base_(s.c_str()), len_(s.size()) {} + constexpr ProgmemStringRef(const char *s, size_t len) : ref_(s, len) {} + constexpr explicit ProgmemStringRef(const StringRef &s) : ref_(s) {} - constexpr const char *progmem_ptr() const { return this->base_; } - constexpr size_t size() const { return this->len_; } - constexpr bool empty() const { return this->len_ == 0; } + constexpr const char *progmem_ptr() const { return this->ref_.c_str(); } + constexpr size_t size() const { return this->ref_.size(); } + constexpr bool empty() const { return this->ref_.empty(); } /// Copy into buf (null terminated, truncated to fit), returns the length copied. size_t write_to(char *buf, size_t buf_size) const { if (buf_size == 0) return 0; - size_t len = std::min(this->len_, buf_size - 1); + size_t len = std::min(this->ref_.size(), buf_size - 1); #ifdef USE_ESP8266 - memcpy_P(buf, this->base_, len); + memcpy_P(buf, this->ref_.c_str(), len); #else - std::memcpy(buf, this->base_, len); + std::memcpy(buf, this->ref_.c_str(), len); #endif buf[len] = '\0'; return len; @@ -251,27 +252,26 @@ class ProgmemStringRef { bool equals(const StringRef &other) const { #ifdef USE_ESP8266 - return other.size() == this->len_ && memcmp_P(other.c_str(), this->base_, this->len_) == 0; + return other.size() == this->ref_.size() && memcmp_P(other.c_str(), this->ref_.c_str(), other.size()) == 0; #else - return other == StringRef(this->base_, this->len_); + return other == this->ref_; #endif } #ifndef USE_ESP8266 /// The string is in RAM here, so it can be viewed directly. - constexpr StringRef ram_ref() const { return StringRef(this->base_, this->len_); } + constexpr const StringRef &ram_ref() const { return this->ref_; } #endif // Remove before 2027.5.0 [[deprecated( "May point to flash; use progmem_ptr() or the owner's accessors. Removed in 2027.5.0")]] constexpr const char * c_str() const { - return this->base_; + return this->ref_.c_str(); } private: - const char *base_{""}; - size_t len_{0}; + StringRef ref_; }; // String conversion functions for ADL compatibility (allows stoi(x) where x is StringRef) diff --git a/tests/components/core/test_string_ref.cpp b/tests/components/core/test_string_ref.cpp index baaf6fe12f8..aaecbb662d8 100644 --- a/tests/components/core/test_string_ref.cpp +++ b/tests/components/core/test_string_ref.cpp @@ -1,9 +1,12 @@ #include +#include "esphome/core/entity_base.h" #include "esphome/core/string_ref.h" #include #include +#include +#include namespace esphome::core::testing { @@ -190,6 +193,13 @@ TEST(ProgmemStringRef, EqualsComparesLengthAndContent) { EXPECT_FALSE(ref.equals(StringRef("Bedroom"))); } +// get_name() must keep returning a reference: external code returns it from `const StringRef &` getters +static_assert(std::is_same_v().ram_ref()), const StringRef &>); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +static_assert(std::is_same_v().get_name()), const StringRef &>); +#pragma GCC diagnostic pop + TEST(ProgmemStringRef, DefaultIsEmpty) { ProgmemStringRef ref; EXPECT_TRUE(ref.empty());