Skip to content

fix(compiler): support C++ equality codegen for std::any in collections and unions#3810

Open
BaldDemian wants to merge 1 commit into
apache:mainfrom
BaldDemian:cpp-any
Open

fix(compiler): support C++ equality codegen for std::any in collections and unions#3810
BaldDemian wants to merge 1 commit into
apache:mainfrom
BaldDemian:cpp-any

Conversation

@BaldDemian

Copy link
Copy Markdown
Contributor

Why?

Fory IDL any maps to std::any in generated C++ code.
std::any does not define operator==, so generated equality code MUST NOT ask the C++ standard library to compare two std::any directly.

But the current C++ compiler still does this in some generated operator== implementations, especially when any appeared inside standard-comparable containers or union alternatives.

For example, generated code such as values_ == other.values_ for std::vector<std::any>,
by_name_ == other.by_name_ for std::unordered_map<std::string, std::any>, or
value_ == other.value_ for std::variant<std::any, ...> causes the standard
container or variant equality operator to instantiate std::any == std::any.

This causes compilation error like:

error: no match for 'operator==' (operand types are 'const std::any' and 'const std::any')

The Rust compiler avoids the equivalent derived-equality problem by not deriving comparison traits for generated types that contain any:

def type_supports_trait(
self,
field_type: FieldType,
trait: str,
parent_stack: Optional[List[Message]] = None,
visiting: Optional[Set[Tuple[str, str, int]]] = None,
) -> bool:
if isinstance(field_type, PrimitiveType):
if field_type.kind == PrimitiveKind.ANY:
return False

The C++ compiler already had an approximate comparison strategy for direct std::any fields instead of trying to compare the stored values:

def get_field_eq_expression(
self, field: Field, parent_stack: Optional[List[Message]]
) -> str:
member_name = self.get_field_member_name(field)
other_member = f"other.{member_name}"
if isinstance(field.field_type, PrimitiveType) and (
field.field_type.kind == PrimitiveKind.ANY
):
return (
f"((!{member_name}.has_value() && !{other_member}.has_value()) || "
f"({member_name}.type() == {other_member}.type()))"
)

What does this PR do?

This PR extends existing approximate comparison approach to every generated equality path that may contain std::any.
The C++ compiler now recursively detects whether a field type contains any;
fields without any keep the existing direct comparison, while fields with any use generated approximate comparison.

  • optional fields compare presence first, then recursively compare the contained value when both sides are present.
  • lists and arrays compare size first, then recursively compare elements in order.
  • maps compare size first, then require every left key to exist in the right map and recursively compare the corresponding values
  • unions compare the active variant index first, then dispatch to the same recursive comparison rule for the active alternative.

Related issues

N/A.

AI Contribution Checklist

  • Substantial AI assistance was used in this PR: no

Does this PR introduce any user-facing change?

N/A.

Benchmark

N/A.

@chaokunyang

Copy link
Copy Markdown
Collaborator

whether we should not generate equals method when message have any field for c++?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants