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.
This commit is contained in:
J. Nick Koston
2026-10-08 19:55:29 -10:00
parent b626711d50
commit 140488723d
5 changed files with 33 additions and 16 deletions
@@ -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<char, OBJECT_ID_MAX_LEN> buf) const {
return this->get_entity()->get_object_id_to(buf);
+4
View File
@@ -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<char, MAX_ICON_LENGTH> buf) const { return this->get_entity()->get_icon_to(buf); }
+2 -2
View File
@@ -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<char, ENTITY_NAME_BUF_SIZE> /*buffer*/) const { return this->name_.ram_ref(); }
const StringRef &get_name() const { return this->name_.ram_ref(); }
const StringRef &get_name_to(std::span<char, ENTITY_NAME_BUF_SIZE> /*buffer*/) const { return this->name_.ram_ref(); }
#endif
bool name_equals(const StringRef &other) const { return this->name_.equals(other); }
+14 -14
View File
@@ -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)
+10
View File
@@ -1,9 +1,12 @@
#include <gtest/gtest.h>
#include "esphome/core/entity_base.h"
#include "esphome/core/string_ref.h"
#include <iterator>
#include <string>
#include <type_traits>
#include <utility>
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<decltype(std::declval<const ProgmemStringRef &>().ram_ref()), const StringRef &>);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
static_assert(std::is_same_v<decltype(std::declval<const EntityBase &>().get_name()), const StringRef &>);
#pragma GCC diagnostic pop
TEST(ProgmemStringRef, DefaultIsEmpty) {
ProgmemStringRef ref;
EXPECT_TRUE(ref.empty());