Skip to content

Extend operator support - #388

Merged
jbcoe merged 3 commits into
mainfrom
push-yspxlxvtypwn
Sep 16, 2026
Merged

jbcoe merged 3 commits into
mainfrom
push-yspxlxvtypwn

Conversation

@jbcoe

@jbcoe jbcoe commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Extend operator forwarding support to more overloadable operators. operator=, operator co_await, the comparison operators, explicit-object members and unary operator& are rejected on an interface.

Fixes a self-assignment bug: protocol's copy/move assignment used this == &other, which a forwarded operator& would hijack; now uses std::addressof.

We opted not to use macros to reduce code verbosity. The limits of C++26 reflection mean that some boilerplate structure for operator thunks is somewhat repetitive.

@jbcoe jbcoe changed the title Modify 9 files Extend operator support Sep 14, 2026
Comment thread name_mangling_tests.cc
Comment thread name_mangling_tests.cc
@jbcoe
jbcoe marked this pull request as draft September 14, 2026 04:12
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.39210% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.97%. Comparing base (3705d23) to head (82cce94).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
vtable.hh 33.33% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #388      +/-   ##
==========================================
+ Coverage   74.48%   80.97%   +6.49%     
==========================================
  Files          15       15              
  Lines        1011     1277     +266     
  Branches      224      214      -10     
==========================================
+ Hits          753     1034     +281     
+ Misses         49       44       -5     
+ Partials      209      199      -10     
Flag Coverage Δ
consteval 89.81% <100.00%> (+4.64%) ⬆️
runtime 79.17% <99.26%> (+7.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jbcoe
jbcoe marked this pull request as ready for review September 14, 2026 04:36
@jbcoe
jbcoe marked this pull request as draft September 14, 2026 04:37
@jbcoe
jbcoe marked this pull request as ready for review September 14, 2026 05:10
@jbcoe
jbcoe requested a review from philipcraig September 14, 2026 05:10

@philipcraig philipcraig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forwarding unary operator& breaks protocol's own assignment. The self-assignment checks at protocol.hh:413 and :439 spell &other, which now resolves to the interface's operator& thunk:

struct I { int operator&() const; int f() const; };
struct T { int operator&() const { return 99; } int f() const { return 3; } };
protocol<I> p{T{}}, q{T{}};
q = p;             // protocol.hh:413: comparison between pointer and integer
q = std::move(p);  // protocol.hh:439: same
auto r = &p;       // int, 99

If the interface's operator& returned a pointer the assignment would compile and compare this against the thunk's result, so genuine self-assignment falls through to destroy-then-copy-from-destroyed. std::addressof(other) at both sites fixes the wrappers (protocol.hh already uses it at 527 and 622). Whether &p on a wrapper should ever dispatch to the interface is a separate decision; at minimum it wants a sentence in the docs. Fine as a follow-up if you prefer.

CONTRIBUTING.md:194-196 still says no operators other than operator() are supported, and DRAFT.md:565-570 still says equality and comparison operators are not generated. Both need updating, or DRAFT.md needs a note that the reference implementation has diverged.

Inline comments cover the comparison-operator regression, a latent explicit-object mangling bug, and some duplication. Any of these can go to follow-up PRs; none needs to hold this one.

Comment thread conformance.hh Outdated
Comment thread conformance.hh
Comment thread name_mangling.hh
Comment thread name_mangling.hh Outdated
Comment thread operator_thunks.hh Outdated
Comment thread operator_thunks.hh
Comment thread protocol_operator_tests.cc Outdated
Comment thread protocol_operator_tests.cc Outdated
Comment thread name_mangling_tests.cc
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh

Fold fixed_operator_code into base_name_of's switch for -Wswitch exhaustiveness
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh

Fold fixed_operator_code into base_name_of's switch for -Wswitch exhaustiveness

Suppress google-runtime-operator for unary operator& test coverage
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh

Fold fixed_operator_code into base_name_of's switch for -Wswitch exhaustiveness

Suppress google-runtime-operator for unary operator& test coverage
@jbcoe

jbcoe commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the detailed review. All should be good to go now.

@jbcoe
jbcoe requested a review from philipcraig September 14, 2026 22:23
@jbcoe
jbcoe enabled auto-merge (squash) September 14, 2026 22:35
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh

Fold fixed_operator_code into base_name_of's switch for -Wswitch exhaustiveness

Suppress google-runtime-operator for unary operator& test coverage

Remove dead operator_thunk/operator_overload_set specializations for the seven comparison operators (==, !=, <, <=, >, >=, <=>).

conformance.hh's is_unsupported_operator rejects these operators on any
protocol interface before generate_member_bases_wrapper can instantiate
operator_overload_set for them, so the specializations were unreachable and
untested. Build and full test suite verified green after removal.
jbcoe added a commit that referenced this pull request Sep 14, 2026
Address PR #388 review: fix operator& self-assignment, exclude comparison operators, update docs and comments, trim duplicated death tests.

Address PR #388 review: share one valueless-check-and-dispatch helper (call_through_vtable) between operator_thunks.hh and member_function_thunks.hh instead of hand-writing the assert in three places.

Fix operator symbol formatting in unsupported-operator diagnostic

Address PR #388 review: reject explicit-object member functions on protocol interfaces, deduplicate call-operator/slicing test concepts into test_helpers.h, cover the unary/binary operator split and const-view dispatch for compound-assignment and increment/decrement, and trim valueless-call death tests to the call operator.


Delete the unused duplicate call_through_vtable in operator_thunks.hh

Fold fixed_operator_code into base_name_of's switch for -Wswitch exhaustiveness

Suppress google-runtime-operator for unary operator& test coverage

Remove dead operator_thunk/operator_overload_set specializations for the seven comparison operators (==, !=, <, <=, >, >=, <=>).

conformance.hh's is_unsupported_operator rejects these operators on any
protocol interface before generate_member_bases_wrapper can instantiate
operator_overload_set for them, so the specializations were unreachable and
untested. Build and full test suite verified green after removal.

Clarify why only the call operator gets a valueless-dispatch death test.

The comment named call_through_vtable, an implementation-detail helper whose
rename would silently invalidate the comment's claim. State the invariant
(one shared valueless check, so one death test covers it) without pinning
the comment to that identifier.
@jbcoe
jbcoe force-pushed the push-yspxlxvtypwn branch 3 times, most recently from 114cdac to 1781ce0 Compare September 15, 2026 20:11
@jbcoe
jbcoe requested a review from RyanJK5 September 15, 2026 20:16
Fold in main's independent valueless_after_move hidden-friend rename
(#395) via the shared call_through_vtable helper; drop protocol_test.cc's
newly-duplicated per-operator valueless-call death tests since one
shared helper only needs one death test to cover it. Relocate the
protocol_view overload-ambiguity regression test (#391) alongside the
other operator* tests in protocol_operator_tests.cc.


wip: fix clang-tidy issues from PR #388

@philipcraig philipcraig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two new things since last round, both filed as issues so they can be handled here or after:

  • #400: call_through_vtable in vtable.hh only uses protocol_object inside the assert, so the installed header fails under -DNDEBUG -Wall -Wextra -Werror with unused parameter 'protocol_object'. main is clean under the same flags. CI has no -Wextra, so it stays green. [[maybe_unused]] fixes it.
  • #401: rejecting ==/<=> turned a member comparison on the interface from silently ignored (what main does and DRAFT.md still says) into a hard consteval throw, so interfaces that compile on main stop compiling, including the = default idiom. Options are in the issue.

Docs still saying the old thing: protocol.hh:32 ("Neither implementation currently supports operators other than operator()"), DRAFT.md:152-154 (protocol does not provide operator* or operator->) and DRAFT.md:565-567 (member comparisons "are not generated"). The protocol.hh line is a one-word fix; the DRAFT.md ones can wait for #396 if that is imminent.

#403: operator new/delete on an interface are dropped by the is_static_member skip with no diagnostic, while the new mangling tests assert codes for them. Probably intended, so a docs decision rather than a code one.

The rest are inline and each points at an issue (#402, #404, #405, #406, #407). Nothing here blocks merging.

Comment thread vtable.hh

template <std::meta::info Member, typename Vtable, typename ProtocolObject,
typename VtablePtr, typename Object, typename... Args>
decltype(auto) call_through_vtable(ProtocolObject* protocol_object,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#400: protocol_object is only used inside the assert, so with NDEBUG this is -Werror=unused-parameter in an installed header. [[maybe_unused]] here; the issue also notes the -O0 cost of the helper not being inline.

Comment thread conformance.hh
}

// Some operators are excluded from protocol interfaces.
consteval bool is_unsupported_operator(std::meta::operators op) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#401: on main a member operator== on the interface was silently ignored; this makes it a hard error, so

struct I { int f() const; bool operator==(const I&) const = default; };

no longer compiles against any candidate. There is also no rejection test for any of the seven comparisons; UnsupportedOperatorsAreRejected covers = and co_await only.

Comment thread conformance.hh
is_operator<std::meta::operators::op_parentheses>(member) ||
is_operator<std::meta::operators::op_square_brackets>(member);
return !is_special_member_function(member) &&
(has_identifier(member) || is_operator_function(member));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#402: operator T() has no identifier and is_operator_function is false for it, so conversion functions fall out here before conformance or rejection sees them. A candidate without the conversion conforms, and static_cast<bool>(p) is ill-formed even when both sides declare it. Pre-existing, but this is now the only operator category that neither forwards nor throws.

Comment thread conformance.hh
declared_argument_count(member) != 0) {
std::vector<std::meta::info> params = parameters_of(member);
if (!params.empty() && is_explicit_object_parameter(params.front())) {
std::string name = has_identifier(member)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#406: this name expression is a copy of the one eight lines up, and operator_diagnostic_name below renders names a third way (no overload, no parameter list). Hoisting name once and nesting the two operator checks under a single if (is_operator_function(member)) removes the duplication and gives every rejection the same wording.

Comment thread CONTRIBUTING.md
checking is O(N\*M) in the number of interface and candidate member
functions.

- Guidance: Developers should refer to `protocol_test.cc`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#407: The deleted Limitations bullet carried four statements that are still true and are now documented nowhere: member function templates are not matched (#393), the generated wrapper does not apply an interface member's ref-qualifier (#356), there is no conversion between wrappers of different interfaces, and conformance checking is O(N*M). They should come back, though they never really belonged in a contributing guide; the issue proposes a doc/limitations.md that this bullet links to, which can also list what #388 rejects. The Supported Features bullet above still names only operator().


#endif

TEST(ReflectionProtocolTest, OperatorSquareBrackets) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#404: from here down each test declares its own Interface/Conforming pair and instantiates all three wrapper kinds, about 132 define_aggregate generations for the TU. It is roughly 3 s of instantiation in every job that builds the tests; one shared interface per operator family halves it in a matched experiment. Test-structure optimisation only, no design question.

Comment thread operator_thunks.hh
Vtable> {
// Build the function-pointer type R(*)(Args...) noexcept(...) from the
// method's return type, parameter types and noexcept-ness.
static consteval std::meta::info fn_ptr_type() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#405: third copy of this builder, after member_function_thunk_for::fn_ptr_type() and the inline one in generate_vtable_specs. vtable.hh's copy already differs (dealias on parameter types, relevant to #397); all three must agree for the thunk specialisation to match the vtable entry. One function_pointer_type_of(info) in vtable.hh would serve all three.

@jbcoe
jbcoe merged commit 36d1673 into main Sep 16, 2026
23 checks passed
@jbcoe
jbcoe deleted the push-yspxlxvtypwn branch September 16, 2026 13:41
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