mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Optimize stateless lambdas to use function pointers (#11551)
This commit is contained in:
		| @@ -16,7 +16,12 @@ from esphome.const import ( | |||||||
|     CONF_UPDATE_INTERVAL, |     CONF_UPDATE_INTERVAL, | ||||||
| ) | ) | ||||||
| from esphome.core import ID | from esphome.core import ID | ||||||
| from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType | from esphome.cpp_generator import ( | ||||||
|  |     LambdaExpression, | ||||||
|  |     MockObj, | ||||||
|  |     MockObjClass, | ||||||
|  |     TemplateArgsType, | ||||||
|  | ) | ||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | ||||||
| from esphome.types import ConfigType | from esphome.types import ConfigType | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
| @@ -87,6 +92,7 @@ def validate_potentially_or_condition(value): | |||||||
|  |  | ||||||
| DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||||
|  | StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action) | ||||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||||
| WhileAction = cg.esphome_ns.class_("WhileAction", Action) | WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||||
| RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) | RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) | ||||||
| @@ -97,9 +103,40 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action) | |||||||
| Automation = cg.esphome_ns.class_("Automation") | Automation = cg.esphome_ns.class_("Automation") | ||||||
|  |  | ||||||
| LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) | LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) | ||||||
|  | StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition) | ||||||
| ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) | ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def new_lambda_pvariable( | ||||||
|  |     id_obj: ID, | ||||||
|  |     lambda_expr: LambdaExpression, | ||||||
|  |     stateless_class: MockObjClass, | ||||||
|  |     template_arg: cg.TemplateArguments | None = None, | ||||||
|  | ) -> MockObj: | ||||||
|  |     """Create Pvariable for lambda, using stateless class if applicable. | ||||||
|  |  | ||||||
|  |     Combines ID selection and Pvariable creation in one call. For stateless | ||||||
|  |     lambdas (empty capture), uses function pointer instead of std::function. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         id_obj: The ID object (action_id, condition_id, or filter_id) | ||||||
|  |         lambda_expr: The lambda expression object | ||||||
|  |         stateless_class: The stateless class to use for stateless lambdas | ||||||
|  |         template_arg: Optional template arguments (for actions/conditions) | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         The created Pvariable | ||||||
|  |     """ | ||||||
|  |     # For stateless lambdas, use function pointer instead of std::function | ||||||
|  |     if lambda_expr.capture == "": | ||||||
|  |         id_obj = id_obj.copy() | ||||||
|  |         id_obj.type = stateless_class | ||||||
|  |  | ||||||
|  |     if template_arg is not None: | ||||||
|  |         return cg.new_Pvariable(id_obj, template_arg, lambda_expr) | ||||||
|  |     return cg.new_Pvariable(id_obj, lambda_expr) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_automation(extra_schema=None, extra_validators=None, single=False): | def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||||
|     if extra_schema is None: |     if extra_schema is None: | ||||||
|         extra_schema = {} |         extra_schema = {} | ||||||
| @@ -240,7 +277,9 @@ async def lambda_condition_to_code( | |||||||
|     args: TemplateArgsType, |     args: TemplateArgsType, | ||||||
| ) -> MockObj: | ) -> MockObj: | ||||||
|     lambda_ = await cg.process_lambda(config, args, return_type=bool) |     lambda_ = await cg.process_lambda(config, args, return_type=bool) | ||||||
|     return cg.new_Pvariable(condition_id, template_arg, lambda_) |     return new_lambda_pvariable( | ||||||
|  |         condition_id, lambda_, StatelessLambdaCondition, template_arg | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_condition( | @register_condition( | ||||||
| @@ -406,7 +445,7 @@ async def lambda_action_to_code( | |||||||
|     args: TemplateArgsType, |     args: TemplateArgsType, | ||||||
| ) -> MockObj: | ) -> MockObj: | ||||||
|     lambda_ = await cg.process_lambda(config, args, return_type=cg.void) |     lambda_ = await cg.process_lambda(config, args, return_type=cg.void) | ||||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) |     return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action( | @register_action( | ||||||
|   | |||||||
| @@ -155,6 +155,7 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon | |||||||
| InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | ||||||
| AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) | AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) | ||||||
| LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||||
|  | StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||||
| SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) | SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) | ||||||
|  |  | ||||||
| _LOGGER = getLogger(__name__) | _LOGGER = getLogger(__name__) | ||||||
| @@ -299,7 +300,7 @@ async def lambda_filter_to_code(config, filter_id): | |||||||
|     lambda_ = await cg.process_lambda( |     lambda_ = await cg.process_lambda( | ||||||
|         config, [(bool, "x")], return_type=cg.optional.template(bool) |         config, [(bool, "x")], return_type=cg.optional.template(bool) | ||||||
|     ) |     ) | ||||||
|     return cg.new_Pvariable(filter_id, lambda_) |     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_filter( | @register_filter( | ||||||
|   | |||||||
| @@ -111,6 +111,21 @@ class LambdaFilter : public Filter { | |||||||
|   std::function<optional<bool>(bool)> f_; |   std::function<optional<bool>(bool)> f_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** Optimized lambda filter for stateless lambdas (no capture). | ||||||
|  |  * | ||||||
|  |  * Uses function pointer instead of std::function to reduce memory overhead. | ||||||
|  |  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||||
|  |  */ | ||||||
|  | class StatelessLambdaFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {} | ||||||
|  |  | ||||||
|  |   optional<bool> new_value(bool value) override { return this->f_(value); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   optional<bool> (*f_)(bool); | ||||||
|  | }; | ||||||
|  |  | ||||||
| class SettleFilter : public Filter, public Component { | class SettleFilter : public Filter, public Component { | ||||||
|  public: |  public: | ||||||
|   optional<bool> new_value(bool value) override; |   optional<bool> new_value(bool value) override; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import re | import re | ||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import LambdaAction | from esphome.automation import LambdaAction, StatelessLambdaAction | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | ||||||
| from esphome.components.esp32.const import ( | from esphome.components.esp32.const import ( | ||||||
| @@ -430,7 +430,9 @@ async def logger_log_action_to_code(config, action_id, template_arg, args): | |||||||
|     text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) |     text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) | ||||||
|  |  | ||||||
|     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) |     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) | ||||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) |     return automation.new_lambda_pvariable( | ||||||
|  |         action_id, lambda_, StatelessLambdaAction, template_arg | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action( | @automation.register_action( | ||||||
| @@ -455,7 +457,9 @@ async def logger_set_level_to_code(config, action_id, template_arg, args): | |||||||
|         text = str(cg.statement(logger.set_log_level(level))) |         text = str(cg.statement(logger.set_log_level(level))) | ||||||
|  |  | ||||||
|     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) |     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) | ||||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) |     return automation.new_lambda_pvariable( | ||||||
|  |         action_id, lambda_, StatelessLambdaAction, template_arg | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| FILTER_SOURCE_FILES = filter_source_files_from_platform( | FILTER_SOURCE_FILES = filter_source_files_from_platform( | ||||||
|   | |||||||
| @@ -261,6 +261,7 @@ ExponentialMovingAverageFilter = sensor_ns.class_( | |||||||
| ) | ) | ||||||
| ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) | ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) | ||||||
| LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) | LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) | ||||||
|  | StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||||
| OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) | OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) | ||||||
| MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) | MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) | ||||||
| ValueListFilter = sensor_ns.class_("ValueListFilter", Filter) | ValueListFilter = sensor_ns.class_("ValueListFilter", Filter) | ||||||
| @@ -573,7 +574,7 @@ async def lambda_filter_to_code(config, filter_id): | |||||||
|     lambda_ = await cg.process_lambda( |     lambda_ = await cg.process_lambda( | ||||||
|         config, [(float, "x")], return_type=cg.optional.template(float) |         config, [(float, "x")], return_type=cg.optional.template(float) | ||||||
|     ) |     ) | ||||||
|     return cg.new_Pvariable(filter_id, lambda_) |     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||||
|  |  | ||||||
|  |  | ||||||
| DELTA_SCHEMA = cv.Schema( | DELTA_SCHEMA = cv.Schema( | ||||||
|   | |||||||
| @@ -296,6 +296,21 @@ class LambdaFilter : public Filter { | |||||||
|   lambda_filter_t lambda_filter_; |   lambda_filter_t lambda_filter_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** Optimized lambda filter for stateless lambdas (no capture). | ||||||
|  |  * | ||||||
|  |  * Uses function pointer instead of std::function to reduce memory overhead. | ||||||
|  |  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||||
|  |  */ | ||||||
|  | class StatelessLambdaFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {} | ||||||
|  |  | ||||||
|  |   optional<float> new_value(float value) override { return this->lambda_filter_(value); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   optional<float> (*lambda_filter_)(float); | ||||||
|  | }; | ||||||
|  |  | ||||||
| /// A simple filter that adds `offset` to each value it receives. | /// A simple filter that adds `offset` to each value it receives. | ||||||
| class OffsetFilter : public Filter { | class OffsetFilter : public Filter { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | |||||||
| # Filters | # Filters | ||||||
| Filter = text_sensor_ns.class_("Filter") | Filter = text_sensor_ns.class_("Filter") | ||||||
| LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) | LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) | ||||||
|  | StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||||
| ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) | ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) | ||||||
| ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) | ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) | ||||||
| AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) | AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) | ||||||
| @@ -70,7 +71,7 @@ async def lambda_filter_to_code(config, filter_id): | |||||||
|     lambda_ = await cg.process_lambda( |     lambda_ = await cg.process_lambda( | ||||||
|         config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) |         config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) | ||||||
|     ) |     ) | ||||||
|     return cg.new_Pvariable(filter_id, lambda_) |     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||||
|  |  | ||||||
|  |  | ||||||
| @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) | @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) | ||||||
|   | |||||||
| @@ -62,6 +62,21 @@ class LambdaFilter : public Filter { | |||||||
|   lambda_filter_t lambda_filter_; |   lambda_filter_t lambda_filter_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** Optimized lambda filter for stateless lambdas (no capture). | ||||||
|  |  * | ||||||
|  |  * Uses function pointer instead of std::function to reduce memory overhead. | ||||||
|  |  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||||
|  |  */ | ||||||
|  | class StatelessLambdaFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {} | ||||||
|  |  | ||||||
|  |   optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   optional<std::string> (*lambda_filter_)(std::string); | ||||||
|  | }; | ||||||
|  |  | ||||||
| /// A simple filter that converts all text to uppercase | /// A simple filter that converts all text to uppercase | ||||||
| class ToUpperFilter : public Filter { | class ToUpperFilter : public Filter { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -79,6 +79,18 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> { | |||||||
|   std::function<bool(Ts...)> f_; |   std::function<bool(Ts...)> f_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /// Optimized lambda condition for stateless lambdas (no capture). | ||||||
|  | /// Uses function pointer instead of std::function to reduce memory overhead. | ||||||
|  | /// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||||
|  | template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {} | ||||||
|  |   bool check(Ts... x) override { return this->f_(x...); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool (*f_)(Ts...); | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component { | template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component { | ||||||
|  public: |  public: | ||||||
|   explicit ForCondition(Condition<> *condition) : condition_(condition) {} |   explicit ForCondition(Condition<> *condition) : condition_(condition) {} | ||||||
| @@ -190,6 +202,19 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> { | |||||||
|   std::function<void(Ts...)> f_; |   std::function<void(Ts...)> f_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /// Optimized lambda action for stateless lambdas (no capture). | ||||||
|  | /// Uses function pointer instead of std::function to reduce memory overhead. | ||||||
|  | /// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||||
|  | template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->f_(x...); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void (*f_)(Ts...); | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class IfAction : public Action<Ts...> { | template<typename... Ts> class IfAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {} |   explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {} | ||||||
|   | |||||||
| @@ -198,6 +198,8 @@ class LambdaExpression(Expression): | |||||||
|         self.return_type = safe_exp(return_type) if return_type is not None else None |         self.return_type = safe_exp(return_type) if return_type is not None else None | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|  |         # Stateless lambdas (empty capture) implicitly convert to function pointers | ||||||
|  |         # when assigned to function pointer types - no unary + needed | ||||||
|         cpp = f"[{self.capture}]({self.parameters})" |         cpp = f"[{self.capture}]({self.parameters})" | ||||||
|         if self.return_type is not None: |         if self.return_type is not None: | ||||||
|             cpp += f" -> {self.return_type}" |             cpp += f" -> {self.return_type}" | ||||||
| @@ -700,6 +702,12 @@ async def process_lambda( | |||||||
|             parts[i * 3 + 1] = var |             parts[i * 3 + 1] = var | ||||||
|         parts[i * 3 + 2] = "" |         parts[i * 3 + 2] = "" | ||||||
|  |  | ||||||
|  |     # All id() references are global variables in generated C++ code. | ||||||
|  |     # Global variables should not be captured - they're accessible everywhere. | ||||||
|  |     # Use empty capture instead of capture-by-value. | ||||||
|  |     if capture == "=": | ||||||
|  |         capture = "" | ||||||
|  |  | ||||||
|     if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: |     if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: | ||||||
|         location = value.esp_range.start_mark |         location = value.esp_range.start_mark | ||||||
|         location.line += value.content_offset |         location.line += value.content_offset | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main): | |||||||
|  |  | ||||||
| def test_text_config_lamda_is_set(generate_main): | def test_text_config_lamda_is_set(generate_main): | ||||||
|     """ |     """ | ||||||
|     Test if lambda is set for lambda mode |     Test if lambda is set for lambda mode (optimized with stateless lambda) | ||||||
|     """ |     """ | ||||||
|     # Given |     # Given | ||||||
|  |  | ||||||
| @@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main): | |||||||
|     main_cpp = generate_main("tests/component_tests/text/test_text.yaml") |     main_cpp = generate_main("tests/component_tests/text/test_text.yaml") | ||||||
|  |  | ||||||
|     # Then |     # Then | ||||||
|     assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp |     assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp | ||||||
|     assert 'return std::string{"Hello"};' in main_cpp |     assert 'return std::string{"Hello"};' in main_cpp | ||||||
|   | |||||||
| @@ -173,6 +173,61 @@ class TestLambdaExpression: | |||||||
|             "}" |             "}" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_str__stateless_no_return(self): | ||||||
|  |         """Test stateless lambda (empty capture) generates correctly""" | ||||||
|  |         target = cg.LambdaExpression( | ||||||
|  |             ('ESP_LOGD("main", "Test message");',), | ||||||
|  |             (),  # No parameters | ||||||
|  |             "",  # Empty capture (stateless) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         actual = str(target) | ||||||
|  |  | ||||||
|  |         assert actual == ('[]() {\n  ESP_LOGD("main", "Test message");\n}') | ||||||
|  |  | ||||||
|  |     def test_str__stateless_with_return(self): | ||||||
|  |         """Test stateless lambda with return type generates correctly""" | ||||||
|  |         target = cg.LambdaExpression( | ||||||
|  |             ("return global_value > 0;",), | ||||||
|  |             (),  # No parameters | ||||||
|  |             "",  # Empty capture (stateless) | ||||||
|  |             bool,  # Return type | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         actual = str(target) | ||||||
|  |  | ||||||
|  |         assert actual == ("[]() -> bool {\n  return global_value > 0;\n}") | ||||||
|  |  | ||||||
|  |     def test_str__stateless_with_params(self): | ||||||
|  |         """Test stateless lambda with parameters generates correctly""" | ||||||
|  |         target = cg.LambdaExpression( | ||||||
|  |             ("return foo + bar;",), | ||||||
|  |             ((int, "foo"), (float, "bar")), | ||||||
|  |             "",  # Empty capture (stateless) | ||||||
|  |             float, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         actual = str(target) | ||||||
|  |  | ||||||
|  |         assert actual == ( | ||||||
|  |             "[](int32_t foo, float bar) -> float {\n  return foo + bar;\n}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def test_str__with_capture(self): | ||||||
|  |         """Test lambda with capture generates correctly""" | ||||||
|  |         target = cg.LambdaExpression( | ||||||
|  |             ("return captured_var + x;",), | ||||||
|  |             ((int, "x"),), | ||||||
|  |             "captured_var",  # Has capture (not stateless) | ||||||
|  |             int, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         actual = str(target) | ||||||
|  |  | ||||||
|  |         assert actual == ( | ||||||
|  |             "[captured_var](int32_t x) -> int32_t {\n  return captured_var + x;\n}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestLiterals: | class TestLiterals: | ||||||
|     @pytest.mark.parametrize( |     @pytest.mark.parametrize( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user