Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/install-gcc-trunk/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ inputs:
# values, 2026-09-13) onward, any -flto build of the tests fails with
# "tree code 'lang_type' is not supported in LTO streams" (GCC PR126017),
# and -fsanitize=null or vptr rejects the `template for` over
# enumerators_of in tutorials/03_reflection.cc with "consteval-only value
# enumerators_of in xyz/tutorials/03_reflection.cc with "consteval-only value
# outside an immediate function context". Restore gcc-latest.deb once a
# snapshot fixes both.
default: https://kayari.org/gcc-latest/gcc-latest_17.0.0-20260906gitb6dabcdc335a.deb
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
- "scripts/bazel.py"
- "scripts/bazel.sh"
- "scripts/compiler_discovery.py"
- "tutorials/BUILD.bazel"
- "xyz/tutorials/BUILD.bazel"
# No `paths:` filter here. The `bazel` summary job is a required status
# check for merging to main, and a workflow that is filtered out by
# `paths:` never reports a check, which leaves unrelated pull requests
Expand Down Expand Up @@ -72,7 +72,7 @@ jobs:
- "scripts/bazel.py"
- "scripts/bazel.sh"
- "scripts/compiler_discovery.py"
- "tutorials/BUILD.bazel"
- "xyz/tutorials/BUILD.bazel"

build:
needs: detect-changes
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
- "scripts/bazel.py"
- "scripts/bazel.sh"
- "scripts/compiler_discovery.py"
- "tutorials/BUILD.bazel"
- "xyz/tutorials/BUILD.bazel"
# No `paths:` filter here. The `sanitizers` summary job is a required
# status check for merging to main, and a workflow that is filtered out by
# `paths:` never reports a check, which leaves unrelated pull requests
Expand Down Expand Up @@ -72,7 +72,7 @@ jobs:
- "scripts/bazel.py"
- "scripts/bazel.sh"
- "scripts/compiler_discovery.py"
- "tutorials/BUILD.bazel"
- "xyz/tutorials/BUILD.bazel"

sanitized-tests:
needs: detect-changes
Expand Down
62 changes: 32 additions & 30 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,56 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

package(default_visibility = ["//visibility:public"])

# Implementation headers. Visible only inside this repository; tests that
# include them depend on this target directly.
cc_library(
name = "name_mangling",
hdrs = ["name_mangling.hh"],
name = "detail",
hdrs = [
"xyz/detail/conformance.hh",
"xyz/detail/conversion_thunks.hh",
"xyz/detail/member_function_thunks.hh",
"xyz/detail/name_mangling.hh",
"xyz/detail/operator_thunks.hh",
"xyz/detail/overload_spec.hh",
"xyz/detail/protocol_traits.hh",
"xyz/detail/protocol_wrappers.hh",
"xyz/detail/vtable.hh",
],
visibility = ["//:__subpackages__"],
)

cc_library(
name = "protocol",
srcs = [
"conformance.hh",
"conversion_thunks.hh",
"member_function_thunks.hh",
"operator_thunks.hh",
"overload_spec.hh",
"protocol_traits.hh",
"protocol_wrappers.hh",
"vtable.hh",
],
hdrs = ["protocol.hh"],
deps = [
":name_mangling",
],
hdrs = ["xyz/protocol.hh"],
deps = [":detail"],
)

cc_library(
name = "consteval_check",
hdrs = ["consteval_check.h"],
hdrs = ["xyz/testing/consteval_check.h"],
)

cc_test(
name = "protocol_test",
size = "small",
srcs = [
"allocator_tests.cc",
"conformance_tests.cc",
"consteval_check_test.cc",
"forwarding_test.cc",
"name_mangling_tests.cc",
"protocol_member_function_tests.cc",
"protocol_operator_tests.cc",
"protocol_test.cc",
"protocol_view_tests.cc",
"tagged_allocator.h",
"test_helpers.h",
"tracking_allocator.h",
"vtable_tests.cc",
"xyz/tests/allocator_tests.cc",
"xyz/tests/conformance_tests.cc",
"xyz/tests/consteval_check_test.cc",
"xyz/tests/forwarding_test.cc",
"xyz/tests/name_mangling_tests.cc",
"xyz/tests/protocol_member_function_tests.cc",
"xyz/tests/protocol_operator_tests.cc",
"xyz/tests/protocol_test.cc",
"xyz/tests/protocol_view_tests.cc",
"xyz/tests/tagged_allocator.h",
"xyz/tests/test_helpers.h",
"xyz/tests/tracking_allocator.h",
"xyz/tests/vtable_tests.cc",
],
deps = [
":consteval_check",
":detail",
":protocol",
"@googletest//:gtest_main",
],
Expand Down
44 changes: 22 additions & 22 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ target_sources(
BASE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}"
FILES
protocol.hh
conformance.hh
conversion_thunks.hh
member_function_thunks.hh
operator_thunks.hh
overload_spec.hh
protocol_traits.hh
protocol_wrappers.hh
vtable.hh
name_mangling.hh)
xyz/protocol.hh
xyz/detail/conformance.hh
xyz/detail/conversion_thunks.hh
xyz/detail/member_function_thunks.hh
xyz/detail/operator_thunks.hh
xyz/detail/overload_spec.hh
xyz/detail/protocol_traits.hh
xyz/detail/protocol_wrappers.hh
xyz/detail/vtable.hh
xyz/detail/name_mangling.hh)
# Consumers need the reflection flags of whichever compiler they build with,
# so the options are generator expressions evaluated at their configure time
# rather than the flags of the compiler that installed the package.
Expand Down Expand Up @@ -182,18 +182,18 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
LINK_LIBRARIES
xyz_protocol::protocol
FILES
protocol_test.cc
protocol_member_function_tests.cc
protocol_operator_tests.cc
protocol_view_tests.cc
conformance_tests.cc
allocator_tests.cc
consteval_check_test.cc
forwarding_test.cc
name_mangling_tests.cc
vtable_tests.cc)

add_subdirectory(tutorials)
xyz/tests/protocol_test.cc
xyz/tests/protocol_member_function_tests.cc
xyz/tests/protocol_operator_tests.cc
xyz/tests/protocol_view_tests.cc
xyz/tests/conformance_tests.cc
xyz/tests/allocator_tests.cc
xyz/tests/consteval_check_test.cc
xyz/tests/forwarding_test.cc
xyz/tests/name_mangling_tests.cc
xyz/tests/vtable_tests.cc)

add_subdirectory(xyz/tutorials)
endif(${BUILD_TESTING})

install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt"
Expand Down
27 changes: 14 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ release, bump `.bazelversion`, which is one of the
The project supports both Bazel and CMake build systems. Prefer Bazel for
day-to-day building and testing: on a two-core codespace a full Bazel build
took 140 s against 223 s for CMake, and rebuilding after a one-character edit
to `protocol.hh` took 45 s against 81 s.
to `xyz/protocol.hh` took 45 s against 81 s.

1. **Bazel** (preferred): To build the project and run tests, execute:

Expand Down Expand Up @@ -110,13 +110,13 @@ configure time when the version it found does not match
Fix genuine findings. Suppress false positives at the site with a `NOLINT`
comment that names the check and gives a reason, or, for a check that does not
apply to a whole directory, in that directory's `.clang-tidy` (see
`tutorials/.clang-tidy`). Checks disabled for the whole repository are listed
with their rationale at the top of `.clang-tidy`.
`xyz/tutorials/.clang-tidy`). Checks disabled for the whole repository are
listed with their rationale at the top of `.clang-tidy`.

### Measuring Test Coverage

Test coverage is measured in two halves, because much of `protocol.hh` is
`consteval` code that executes inside the compiler where runtime coverage
Test coverage is measured in two halves, because much of `xyz/protocol.hh`
is `consteval` code that executes inside the compiler where runtime coverage
instrumentation cannot see it.

Runtime coverage uses GCC's gcov:
Expand Down Expand Up @@ -177,7 +177,7 @@ constraints.

Interfaces for `protocol` and `protocol_view` are plain structs or classes.
Their public, non-virtual, non-template member functions define the protocol;
`protocol.hh` inspects them with C++26 reflection at compile time.
`xyz/protocol.hh` inspects them with C++26 reflection at compile time.

### Supported Features

Expand All @@ -195,19 +195,20 @@ Their public, non-virtual, non-template member functions define the protocol;
functions are never called, whether declared on the interface or on the
conforming type.

- Guidance: Developers should refer to `protocol_test.cc`,
`forwarding_test.cc`, `allocator_tests.cc` and `tutorials/reflection.cc` for
examples of supported interface patterns.
- Guidance: Developers should refer to `xyz/tests/protocol_test.cc`,
`xyz/tests/forwarding_test.cc`, `xyz/tests/allocator_tests.cc` and
`xyz/tutorials/03_reflection.cc` for examples of supported interface
patterns.

## Usage Examples

Examples of how to use the `protocol` library can be found within the test
suite.

- `protocol_test.cc`: This file contains tests that demonstrate the library's
intended usage. It shows how to define an interface and then instantiate
and use the wrapper with concrete types. Developers can examine this file
for practical examples.
- `xyz/tests/protocol_test.cc`: This file contains tests that demonstrate the
library's intended usage. It shows how to define an interface and then
instantiate and use the wrapper with concrete types. Developers can examine
this file for practical examples.

## Current Status and Limitations

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ structural-subtyping, `protocol` is owning, `protocol_view` is non-owning.
See [DRAFT.md](DRAFT.md) for more details on design.

This repository contains both the ISO C++ proposal to add these new library
types and a reference implementation, `protocol.hh`, built on C++26 reflection
(P2996). It requires a compiler with reflection support, such as a GCC trunk
snapshot with `-freflection`.
types and a reference implementation, `xyz/protocol.hh`, built on C++26
reflection (P2996). It requires a compiler with reflection support, such as a
GCC trunk snapshot with `-freflection`.

## Standardization

Expand Down
2 changes: 1 addition & 1 deletion cmake/modules/FindClangTidy.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ endfunction()

if (CLANG_TIDY_ENABLE)
# No CONFIG_FILE: passing --config-file disables clang-tidy's
# per-directory lookup, which is needed for tutorials/.clang-tidy to
# per-directory lookup, which is needed for xyz/tutorials/.clang-tidy to
# override the repository configuration. The repository .clang-tidy is
# still found by walking up from each source file.
enable_clang_tidy()
Expand Down
6 changes: 3 additions & 3 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ codecov:
after_n_builds: 2

ignore:
- "*_test.cc"
- "*_tests.cc"
- "tutorials/**"
- "xyz/tests/**"
- "xyz/testing/**"
- "xyz/tutorials/**"

coverage:
status:
Expand Down
4 changes: 2 additions & 2 deletions install_test/main.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "protocol.hh"
#include "xyz/protocol.hh"

struct Shape {
double area() const;
Expand All @@ -11,6 +11,6 @@ struct Square {
};

int main() {
xyz::reflection::protocol<Shape> shape(Square{2.0});
xyz::protocol<Shape> shape(Square{2.0});
return shape.area() == 4.0 ? 0 : 1;
}
49 changes: 30 additions & 19 deletions scripts/consteval_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,20 @@

SOURCE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def source_files(suffix: str) -> list[str]:
"""Repository-relative paths of the files under xyz/ ending in `suffix`."""
return sorted(
os.path.relpath(path, SOURCE_ROOT)
for path in glob.glob(
os.path.join(SOURCE_ROOT, "xyz", "**", f"*{suffix}"), recursive=True
)
)


# Library headers use the .hh suffix; test helpers (.h) are not instrumented.
INSTRUMENTED_HEADERS = sorted(
os.path.basename(path) for path in glob.glob(os.path.join(SOURCE_ROOT, "*.hh"))
)
INSTRUMENTED_HEADERS = source_files(".hh")
HELPER_HEADERS = source_files(".h")

TRAP_HEADER = "consteval_coverage_probe.h"

Expand All @@ -79,17 +89,17 @@
# Translation units that instantiate protocols and so drive the consteval
# machinery in the instrumented headers, cheapest to compile first: each
# only arms the probe points the ones before it left uncovered.
# protocol_member_function_tests.cc is left out: it reaches no probe point
# the others miss.
# xyz/tests/protocol_member_function_tests.cc is left out: it reaches no probe
# point the others miss.
DEFAULT_TRANSLATION_UNITS = [
"vtable_tests.cc",
"name_mangling_tests.cc",
"conformance_tests.cc",
"allocator_tests.cc",
"protocol_operator_tests.cc",
"protocol_test.cc",
"protocol_view_tests.cc",
"forwarding_test.cc",
"xyz/tests/vtable_tests.cc",
"xyz/tests/name_mangling_tests.cc",
"xyz/tests/conformance_tests.cc",
"xyz/tests/allocator_tests.cc",
"xyz/tests/protocol_operator_tests.cc",
"xyz/tests/protocol_test.cc",
"xyz/tests/protocol_view_tests.cc",
"xyz/tests/forwarding_test.cc",
]

ProbeKey = tuple[int, int] # (header index, line number)
Expand Down Expand Up @@ -427,7 +437,7 @@ def populate_work_dir(
"""
Fill `work_dir` with the instrumented headers and everything they need.

Every root header and each probe translation unit is copied, so that
Every header and each probe translation unit is copied, so that
every quote-include resolves inside the work directory; nothing falls
back to an uninstrumented original. A previous run's directory is
replaced; any other existing directory is left alone.
Expand All @@ -439,14 +449,13 @@ def populate_work_dir(
shutil.rmtree(work_dir)
os.makedirs(work_dir)
open(marker, "w").close()
for pattern in ("*.h", "*.hh"):
for path in glob.glob(os.path.join(SOURCE_ROOT, pattern)):
shutil.copy(path, work_dir)
for path in INSTRUMENTED_HEADERS + HELPER_HEADERS + translation_units:
destination = os.path.join(work_dir, path)
os.makedirs(os.path.dirname(destination), exist_ok=True)
shutil.copy(os.path.join(SOURCE_ROOT, path), destination)
for header, lines in instrumented_by_header.items():
with open(os.path.join(work_dir, header), "w") as out:
out.write("\n".join(lines) + "\n")
for translation_unit in translation_units:
shutil.copy(os.path.join(SOURCE_ROOT, translation_unit), work_dir)


def compile_batch(
Expand All @@ -469,6 +478,8 @@ def compile_batch(
"-w",
"-include",
trap_header,
"-iquote",
configuration.work_dir,
]
for directory in configuration.include_directories:
command.extend(["-I", directory])
Expand Down
File renamed without changes.
Loading
Loading