diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 3af0e609a8..398e25f69e 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,13 +8,25 @@ namespace esphome::climate { static const std::vector EMPTY_CUSTOM_MODES; // NOLINT const std::vector &ClimateTraits::get_supported_custom_fan_modes() const { - // Compat: return empty ref when pointer is null. Remove in 2026.11.0 (change return to const vector *). - return this->supported_custom_fan_modes_ ? *this->supported_custom_fan_modes_ : EMPTY_CUSTOM_MODES; + if (this->supported_custom_fan_modes_) { + return *this->supported_custom_fan_modes_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_fan_modes_.empty()) { + return this->compat_custom_fan_modes_; + } + return EMPTY_CUSTOM_MODES; } const std::vector &ClimateTraits::get_supported_custom_presets() const { - // Compat: return empty ref when pointer is null. Remove in 2026.11.0 (change return to const vector *). - return this->supported_custom_presets_ ? *this->supported_custom_presets_ : EMPTY_CUSTOM_MODES; + if (this->supported_custom_presets_) { + return *this->supported_custom_presets_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_presets_.empty()) { + return this->compat_custom_presets_; + } + return EMPTY_CUSTOM_MODES; } int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 86ebf33c48..939bf96ed5 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -148,7 +148,8 @@ class ClimateTraits { bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !this->supported_fan_modes_.empty() || - (this->supported_custom_fan_modes_ && !this->supported_custom_fan_modes_->empty()); + (this->supported_custom_fan_modes_ && !this->supported_custom_fan_modes_->empty()) || + !this->compat_custom_fan_modes_.empty(); // Compat: remove in 2026.11.0 } const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; } @@ -159,19 +160,21 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(std::initializer_list modes) { - // NOLINT - intentional leak: pointer must survive copies of ClimateTraits - this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT + // Compat: store in owned vector. Copies copy the vector (same cost as before this PR). + this->compat_custom_fan_modes_ = modes; } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(const std::vector &modes) { - this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT + this->compat_custom_fan_modes_ = modes; } // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. const std::vector &get_supported_custom_fan_modes() const; bool supports_custom_fan_mode(const char *custom_fan_mode) const { - return this->supported_custom_fan_modes_ && vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode); + return (this->supported_custom_fan_modes_ && + vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode)) || + vector_contains(this->compat_custom_fan_modes_, custom_fan_mode); // Compat: remove in 2026.11.0 } bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { return this->supports_custom_fan_mode(custom_fan_mode.c_str()); @@ -190,18 +193,19 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(std::initializer_list presets) { - this->supported_custom_presets_ = new std::vector(presets); // NOLINT + this->compat_custom_presets_ = presets; } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(const std::vector &presets) { - this->supported_custom_presets_ = new std::vector(presets); // NOLINT + this->compat_custom_presets_ = presets; } // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. const std::vector &get_supported_custom_presets() const; bool supports_custom_preset(const char *custom_preset) const { - return this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset); + return (this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset)) || + vector_contains(this->compat_custom_presets_, custom_preset); // Compat: remove in 2026.11.0 } bool supports_custom_preset(const std::string &custom_preset) const { return this->supports_custom_preset(custom_preset.c_str()); @@ -270,8 +274,11 @@ class ClimateTraits { return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); } const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const { - return this->supported_custom_fan_modes_ ? vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len) - : nullptr; + if (this->supported_custom_fan_modes_) { + return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_fan_modes_, custom_fan_mode, len); } /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found @@ -280,8 +287,11 @@ class ClimateTraits { return this->find_custom_preset_(custom_preset, strlen(custom_preset)); } const char *find_custom_preset_(const char *custom_preset, size_t len) const { - return this->supported_custom_presets_ ? vector_find(*this->supported_custom_presets_, custom_preset, len) - : nullptr; + if (this->supported_custom_presets_) { + return vector_find(*this->supported_custom_presets_, custom_preset, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_presets_, custom_preset, len); } uint32_t feature_flags_{0}; @@ -304,6 +314,10 @@ class ClimateTraits { */ const std::vector *supported_custom_fan_modes_{nullptr}; const std::vector *supported_custom_presets_{nullptr}; + // Compat: owned storage for deprecated setters. Copies copy the vector (same cost as pre-PR). + // Remove in 2026.11.0. + std::vector compat_custom_fan_modes_; + std::vector compat_custom_presets_; }; } // namespace esphome::climate