refactor(eventsub): use variants for sum types (#5930)

This commit is contained in:
nerix
2025-02-08 14:33:29 +01:00
committed by GitHub
parent 0d02a6344b
commit 992f9195a7
25 changed files with 925 additions and 684 deletions
@@ -17,6 +17,7 @@ def _generate_implementation(header_path: str, items: list[Struct | Enum]) -> st
doc = f"""// WARNING: This file is automatically generated. Any changes will be lost.
#include "{header_path}"
#include "twitch-eventsub-ws/chrono.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/detail/variant.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/detail/errors.hpp"
#include <boost/json.hpp>
+38 -1
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Optional
from dataclasses import dataclass
import logging
@@ -54,6 +55,19 @@ def _is_trivially_copyable(type: clang.cindex.Type) -> bool:
return _is_chrono_like_type(type)
def _has_no_fields(type: clang.cindex.Type) -> bool:
for _ in type.get_fields():
return False
return True
@dataclass
class VariantType:
name: str
trivial: bool
empty: bool
class Member:
def __init__(
self,
@@ -68,6 +82,8 @@ class Member:
self.type_name = type_name
self.tag: Optional[str] = None
self.trivial = trivial
self.variant_types: list[VariantType] | None = None
self.variant_fallback: str | None = None
self.dont_fail_on_deserialization: bool = False
@@ -124,7 +140,8 @@ class Member:
overwrite_member_type = MemberType.OPTIONAL_VECTOR
case other:
log.warning(f"Vector cannot be added on top of other member type: {other}")
case "variant":
overwrite_member_type = MemberType.VARIANT
case other:
log.warning(f"Unhandled template type: {other}")
@@ -144,8 +161,28 @@ class Member:
member.apply_comment_commands(comment_commands)
if member.member_type == MemberType.VARIANT:
member.apply_variant(node.type, namespace)
return member
def apply_variant(self, type: clang.cindex.Type, namespace: tuple[str, ...]):
self.variant_types = []
for idx in range(type.get_num_template_arguments()):
inner = type.get_template_argument_type(idx)
name = get_type_name(inner, namespace)
if name == "std::string" or name == "String":
assert not self.variant_fallback
self.variant_fallback = name
continue
self.variant_types.append(
VariantType(
name=name,
trivial=_is_trivially_copyable(inner),
empty=_has_no_fields(inner),
)
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return False
@@ -6,3 +6,4 @@ class MemberType(Enum):
VECTOR = 2
OPTIONAL = 3
OPTIONAL_VECTOR = 4
VARIANT = 5
@@ -0,0 +1,49 @@
{% if not field.tag -%}
static_assert(false, "{{field.name}} doesn't have a json_tag");
{%- endif %}
const auto *jv{{field.name}}Tag = root.if_contains("{{field.tag}}");
if (jv{{field.name}}Tag == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto {{field.name}}TagRes = boost::json::try_value_to<boost::json::string>(*jv{{field.name}}Tag);
if ({{field.name}}TagRes.has_error())
{
return {{field.name}}TagRes.error();
}
std::string_view {{field.name}}Tag = *{{field.name}}TagRes;
decltype(std::declval<{{struct.full_name}}>().{{field.name}}) {{field.name}};
{%- for type in field.variant_types %}
{%- if not loop.first %}else{% endif %} if ({{field.name}}Tag == {{type.name}}::TAG)
{
{%- if type.empty -%}
{{field.name}}.emplace<{{type.name}}>();
{%- else -%}
const auto *{{field.name}}Val = root.if_contains(detail::fieldFor<{{type.name}}>());
if (!{{field.name}}Val)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto {{field.name}}{{type.name}} = boost::json::try_value_to<{{type.name}}>(*{{field.name}}Val);
if ({{field.name}}{{type.name}}.has_error())
{
return {{field.name}}{{type.name}}.error();
}
{% if type.trivial -%}
{{field.name}}.emplace<{{type.name}}>({{field.name}}{{type.name}}.value());
{%- else -%}
{{field.name}}.emplace<{{type.name}}>(std::move({{field.name}}{{type.name}}.value()));
{%- endif %}
{%- endif -%}
}
{%- endfor -%}
else
{
{%- if field.variant_fallback -%}
{{field.name}}.emplace<{{field.variant_fallback}}>({{field.name}}Tag);
{%- else -%}
EVENTSUB_BAIL_HERE(error::Kind::UnknownVariant);
{%- endif -%}
}
@@ -0,0 +1 @@
.{{field.name}} = std::move({{field.name}}),
@@ -1,5 +1,5 @@
boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &jvRoot)
boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &{%- if struct.members|length -%}jvRoot{%- else -%}/* jvRoot */{%- endif -%})
{
{% if struct.inner_root %}
if (!jvRoot.is_object())
@@ -38,6 +38,8 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo
{% include 'field-optional.tmpl' indent content %}
{%- elif field.member_type == MemberType.OPTIONAL_VECTOR -%}
{% include 'field-optional-vector.tmpl' indent content %}
{%- elif field.member_type == MemberType.VARIANT -%}
{% include 'field-variant.tmpl' indent content %}
{%- endif -%}
{% endfor %}
@@ -51,6 +53,8 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo
{% include 'initializer-optional.tmpl' indent content %}
{%- elif field.member_type == MemberType.OPTIONAL_VECTOR -%}
{% include 'initializer-optional-vector.tmpl' indent content %}
{%- elif field.member_type == MemberType.VARIANT -%}
{% include 'initializer-variant.tmpl' indent content %}
{%- endif -%}
{% endfor %}
};
+2
View File
@@ -68,6 +68,8 @@ class Walker:
if type is None:
# Skip nodes without a type
return False
if node.storage_class == clang.cindex.StorageClass.STATIC:
return False
# log.debug(f"{struct}: {type.spelling} {node.spelling} ({type.kind})")
if struct: