Merge remote-tracking branch 'upstream/templatable-value-specialize' into test_merge_to_beta_again

This commit is contained in:
J. Nick Koston
2026-04-07 22:47:07 -10:00
4 changed files with 14 additions and 13 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ async def globals_set_to_code(config, action_id, template_arg, args):
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
var = cg.new_Pvariable(action_id, template_arg, paren)
templ = await cg.templatable(
config[CONF_VALUE], args, None, to_exp=cg.RawExpression
config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True
)
cg.add(var.set_value(templ))
return var
+2
View File
@@ -40,6 +40,7 @@ template<int... S> struct gens<0, S...> { using type = seq<S...>; };
template<typename T, typename... X> class TemplatableFn {
public:
TemplatableFn() = default;
TemplatableFn(std::nullptr_t) = delete;
// Exact return type match — direct function pointer storage
template<typename F> TemplatableFn(F f) requires std::convertible_to<F, T (*)(X...)> : f_(f) {}
@@ -102,6 +103,7 @@ using TemplatableStorage =
template<typename T, typename... X> class TemplatableValue {
public:
TemplatableValue() = default;
TemplatableValue(std::nullptr_t) = delete;
// Accept raw constants
template<typename V> TemplatableValue(V value) requires(!std::invocable<V, X...>) : tag_(VALUE) {
+4 -3
View File
@@ -819,6 +819,8 @@ async def templatable(
args: list[tuple[SafeExpType, str]],
output_type: SafeExpType | None,
to_exp: Callable | dict = None,
*,
wrap_constant: bool = False,
):
"""Generate code for a templatable config option.
@@ -850,9 +852,8 @@ async def templatable(
return FlashStringLiteral(value)
# Wrap non-string constants in stateless lambdas so that TemplatableFn
# (used by TEMPLATABLE_VALUE macro) stores them as function pointers.
# When output_type is None, the lambda omits the return type annotation
# and the C++ compiler deduces it (used by globals where T is unknown).
if output_type is not std_string:
# wrap_constant=True forces wrapping even with output_type=None (compiler deduces type).
if (output_type is not None or wrap_constant) and output_type is not std_string:
return LambdaExpression(
f"return {safe_exp(value)};",
args,
+7 -9
View File
@@ -652,11 +652,11 @@ async def test_templatable__empty_string_with_std_string() -> None:
@pytest.mark.asyncio
async def test_templatable__string_with_none_output_type() -> None:
"""Static string with output_type=None returns stateless lambda (no return type)."""
"""Static string with output_type=None returns raw string (no wrapping)."""
result = await cg.templatable("hello", [], None)
assert isinstance(result, cg.LambdaExpression)
assert result.capture == ""
assert isinstance(result, str)
assert result == "hello"
@pytest.mark.asyncio
@@ -678,11 +678,10 @@ async def test_templatable__string_with_non_string_output_type() -> None:
@pytest.mark.asyncio
async def test_templatable__with_to_exp_callable() -> None:
"""When to_exp is provided with output_type=None, result is lambda-wrapped."""
"""When to_exp is provided, it is applied to non-template values."""
result = await cg.templatable(42, [], None, to_exp=lambda x: x * 2)
assert isinstance(result, cg.LambdaExpression)
assert result.capture == ""
assert result == 84
@pytest.mark.asyncio
@@ -696,12 +695,11 @@ async def test_templatable__with_to_exp_callable_and_output_type() -> None:
@pytest.mark.asyncio
async def test_templatable__with_to_exp_dict() -> None:
"""When to_exp is a dict, value is looked up and lambda-wrapped."""
"""When to_exp is a dict, value is looked up."""
mapping: dict[str, int] = {"on": 1, "off": 0}
result = await cg.templatable("on", [], None, to_exp=mapping)
assert isinstance(result, cg.LambdaExpression)
assert result.capture == ""
assert result == 1
@pytest.mark.asyncio