diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ca68ba5c0..c99f2a345 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,7 @@ # # Copyright (c) 2023 Christian Mazakas # Copyright (c) 2023 Alan de Freitas +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -249,6 +250,21 @@ jobs: working-directory: boost-root/libs/capy/doc run: node lint/doc-lint.mjs + # BLOCKING, and deliberately outside the baseline/comparator posture above: + # this is not a style rule with a backlog to burn down, it is a structural + # check that every `include::` still resolves. Pages take code straight from + # the compiled snippets and, since #381, from the public headers, so an + # include naming a tag that no longer exists renders an EMPTY block rather + # than failing. Asciidoctor exits 0 on that (warning only) and the Antora + # leg above only asserts that build/site exists, so nothing else here would + # catch a marker deleted during ordinary refactoring. It stands at zero + # violations, so there is nothing to grandfather. + - name: "Lint: include tags resolve" + if: always() + continue-on-error: false + working-directory: boost-root/libs/capy/doc + run: node lint/check-include-tags.mjs + - name: "Lint: accessibility (pa11y-ci)" if: always() continue-on-error: true diff --git a/doc/antora.yml b/doc/antora.yml index cf151276e..f06147b2b 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,5 +1,6 @@ # # Copyright (c) 2025 Mohammad Nejati +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -28,6 +29,16 @@ ext: into: modules/ROOT/examples/snippets - dir: test/doc/programs into: modules/ROOT/examples/programs + # Public headers carrying `tag::`/`end::` markers, so pages can include a + # definition straight from the library instead of keeping a copy that + # drifts. Scanned under their real path, so an `example$include/...` + # target in a page reads as the header it actually is. + - dir: include + files: + - boost/capy/concept/*.hpp + - boost/capy/buffers.hpp + - boost/capy/ex/frame_allocator.hpp + into: modules/ROOT/examples/include cpp-reference: config: doc/mrdocs.yml cpp-tagfiles: diff --git a/doc/lint/check-include-tags.mjs b/doc/lint/check-include-tags.mjs new file mode 100644 index 000000000..c367729d5 --- /dev/null +++ b/doc/lint/check-include-tags.mjs @@ -0,0 +1,145 @@ +#!/usr/bin/env node +// +// check-include-tags.mjs — every `include::example$...[tag=...]` in a page +// must resolve to a real file that really carries that tag. Node built-ins +// only, no dependencies. +// +// Why this exists: pages pull code out of compiled snippets and, since +// issue #381, straight out of the public headers, so a definition shown on +// a page cannot drift from the definition that ships. That moves the risk +// rather than removing it. Asciidoctor treats a missing include tag as a +// WARNING and still exits 0 — verified locally: a page referencing a +// nonexistent tag renders an empty listing block and the build succeeds. +// The Antora CI leg cannot catch it either; it only asserts that +// `build/site` exists, precisely because Antora also exits 0 on failure. +// +// So without this gate, deleting a `tag::`/`end::` marker from a header +// during ordinary refactoring silently empties whatever page included it, +// and nothing goes red. That is a worse failure than the drift it replaced: +// drift is at least visible on the page. +// +// The example$ -> repo-path mapping is read out of doc/antora.yml's +// collector scan config rather than hardcoded here, so adding a scan entry +// cannot leave this check behind. +// +// Blocking: exits 1 on any unresolved include target or missing tag. +// +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const DOC_DIR = path.resolve(SCRIPT_DIR, '..'); +const REPO_ROOT = path.resolve(DOC_DIR, '..'); +const PAGES_DIR = path.join(DOC_DIR, 'modules', 'ROOT', 'pages'); +const EXAMPLES_PREFIX = 'modules/ROOT/examples'; + +// Parse the `dir:`/`into:` pairs under ext.collector.scan in antora.yml. +// A full YAML parser would be a dependency; the block is a flat list of +// two- and three-key entries, so an indentation-aware line scan is enough. +function readScanMap(yamlPath) { + const lines = fs.readFileSync(yamlPath, 'utf8').split('\n'); + const entries = []; + let cur = null; + for (const line of lines) { + if (/^\s*#/.test(line)) continue; + const dir = line.match(/^\s*-\s*dir:\s*(\S+)\s*$/); + if (dir) { + if (cur) entries.push(cur); + cur = { dir: dir[1], into: null }; + continue; + } + const into = line.match(/^\s*into:\s*(\S+)\s*$/); + if (into && cur) { + cur.into = into[1]; + entries.push(cur); + cur = null; + } + } + if (cur) entries.push(cur); + + // example$/ -> /. Longest prefix wins, so a nested + // mapping such as examples/snippets is preferred over bare examples. + return entries + .filter((e) => e.into && e.into.startsWith(EXAMPLES_PREFIX)) + .map((e) => ({ + prefix: e.into.slice(EXAMPLES_PREFIX.length).replace(/^\//, ''), + dir: e.dir, + })) + .sort((a, b) => b.prefix.length - a.prefix.length); +} + +function resolveTarget(resource, scanMap) { + for (const { prefix, dir } of scanMap) { + if (prefix === '') return path.join(REPO_ROOT, dir, resource); + if (resource === prefix || resource.startsWith(prefix + '/')) { + return path.join(REPO_ROOT, dir, resource.slice(prefix.length).replace(/^\//, '')); + } + } + return null; +} + +// tag=a | tags=a;b;!c | tags=a,b — negations and wildcards select +// nothing on their own, so they are not names this check can verify. +function tagsOf(attrs) { + const m = attrs.match(/\btags?=([^,\]]*)/); + if (!m) return []; + return m[1] + .split(/[;,]/) + .map((t) => t.trim()) + .filter((t) => t && !t.startsWith('!') && !t.includes('*')); +} + +function walk(dir, out = []) { + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, e.name); + if (e.isDirectory()) walk(p, out); + else if (e.name.endsWith('.adoc')) out.push(p); + } + return out; +} + +const scanMap = readScanMap(path.join(DOC_DIR, 'antora.yml')); +if (scanMap.length === 0) { + console.error('check-include-tags: no collector scan entries found in antora.yml'); + process.exit(1); +} + +const violations = []; +let checked = 0; + +for (const page of walk(PAGES_DIR)) { + const rel = path.relative(REPO_ROOT, page); + const lines = fs.readFileSync(page, 'utf8').split('\n'); + lines.forEach((line, i) => { + const m = line.match(/^include::example\$(\S+?)\[([^\]]*)\]/); + if (!m) return; + const [, resource, attrs] = m; + const where = `${rel}:${i + 1}`; + const target = resolveTarget(resource, scanMap); + if (!target) { + violations.push(`${where}: example$${resource} matches no collector scan entry`); + return; + } + if (!fs.existsSync(target)) { + violations.push(`${where}: example$${resource} resolves to a missing file (${path.relative(REPO_ROOT, target)})`); + return; + } + const body = fs.readFileSync(target, 'utf8'); + for (const tag of tagsOf(attrs)) { + checked++; + const has = body.includes(`tag::${tag}[]`) && body.includes(`end::${tag}[]`); + if (!has) { + violations.push(`${where}: tag '${tag}' not found in ${path.relative(REPO_ROOT, target)}`); + } + } + }); +} + +if (violations.length) { + console.error(`check-include-tags: ${violations.length} violation(s)\n`); + for (const v of violations) console.error(` ${v}`); + process.exit(1); +} + +console.log(`check-include-tags: OK — ${checked} tagged include(s) resolve to a live tag.`); diff --git a/doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc b/doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc index 44cb3af54..5f22fd406 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc @@ -238,7 +238,7 @@ cpp:safe_resume[]'s implementation: [source,cpp] ---- -include::example$snippets/9k_executor.cpp[tag=safe_resume] +include::example$include/boost/capy/ex/frame_allocator.hpp[tag=safe_resume] ---- The cost is two TLS accesses (one read, one write) per `.resume()` call, negligible compared to the cost of resuming a coroutine. diff --git a/doc/modules/ROOT/pages/5.buffers/5a.buffers.adoc b/doc/modules/ROOT/pages/5.buffers/5a.buffers.adoc index 11295e81c..79e43f1d4 100644 --- a/doc/modules/ROOT/pages/5.buffers/5a.buffers.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5a.buffers.adoc @@ -111,14 +111,14 @@ Treating a single buffer as a one-element sequence is deliberate. It lets one co [source,cpp] ---- -include::example$snippets/5c_sequences.cpp[tag=const_buffer_sequence_concept,indent=0] +include::example$include/boost/capy/buffers.hpp[tag=const_buffer_sequence_concept,indent=0] ---- A type satisfies cpp:ConstBufferSequence[] if it converts to cpp:const_buffer[] directly, or if it is a bidirectional range whose elements convert to cpp:const_buffer[]. [source,cpp] ---- -include::example$snippets/5c_sequences.cpp[tag=mutable_buffer_sequence_concept,indent=0] +include::example$include/boost/capy/buffers.hpp[tag=mutable_buffer_sequence_concept,indent=0] ---- cpp:MutableBufferSequence[] follows the same pattern for cpp:mutable_buffer[]. diff --git a/doc/modules/ROOT/pages/6.streams/6b.streams.adoc b/doc/modules/ROOT/pages/6.streams/6b.streams.adoc index 96f09e72e..65383cf42 100644 --- a/doc/modules/ROOT/pages/6.streams/6b.streams.adoc +++ b/doc/modules/ROOT/pages/6.streams/6b.streams.adoc @@ -7,7 +7,7 @@ A type satisfies cpp:ReadStream[] if it provides partial read operations via `re [source,cpp] ---- -include::example$snippets/6b_streams.cpp[tag=read_stream_concept] +include::example$include/boost/capy/concept/read_stream.hpp[tag=read_stream_concept] ---- The `requires` clause names a single representative buffer (cpp:mutable_buffer_archetype[]) because a {cpp} concept cannot say "works with every buffer sequence." The real contract is that `read_some` accepts *any* cpp:MutableBufferSequence[]—one buffer or a range; the archetype only samples that requirement. @@ -40,7 +40,7 @@ A type satisfies cpp:WriteStream[] if it provides partial write operations via ` [source,cpp] ---- -include::example$snippets/6b_streams.cpp[tag=write_stream_concept] +include::example$include/boost/capy/concept/write_stream.hpp[tag=write_stream_concept] ---- As with cpp:ReadStream[], the cpp:const_buffer_archetype[] is only a representative: the real contract is that `write_some` accepts *any* cpp:ConstBufferSequence[], which a {cpp} concept cannot fully express. diff --git a/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc b/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc index c24fb0aba..9ff8211cc 100644 --- a/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc +++ b/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc @@ -9,7 +9,7 @@ cpp:ReadStream[] is the fundamental partial-read primitive among Capy's stream c [source,cpp] ---- -include::example$snippets/9c_read_stream.cpp[tag=concept_definition,indent=0] +include::example$include/boost/capy/concept/read_stream.hpp[tag=read_stream_concept,indent=0] ---- The `requires` clause checks `read_some` against a single representative buffer, cpp:mutable_buffer_archetype[], because a {cpp} concept cannot quantify over "every buffer sequence." The contract is stronger than what the compiler verifies: a cpp:ReadStream[] must accept *any* cpp:MutableBufferSequence[]—a single buffer or a range of them—and the archetype stands in for that universally-quantified requirement. diff --git a/doc/modules/ROOT/pages/9.design/9f.WriteStream.adoc b/doc/modules/ROOT/pages/9.design/9f.WriteStream.adoc index 2025b5640..e0d3dcfaf 100644 --- a/doc/modules/ROOT/pages/9.design/9f.WriteStream.adoc +++ b/doc/modules/ROOT/pages/9.design/9f.WriteStream.adoc @@ -9,7 +9,7 @@ cpp:WriteStream[] is the fundamental partial-write primitive among Capy's stream [source,cpp] ---- -include::example$snippets/9f_write_stream.cpp[tag=write_stream_concept] +include::example$include/boost/capy/concept/write_stream.hpp[tag=write_stream_concept] ---- The `requires` clause checks `write_some` against a single representative buffer, cpp:const_buffer_archetype[], because a {cpp} concept cannot quantify over "every buffer sequence." The contract is stronger than what the compiler verifies: a cpp:WriteStream[] must accept *any* cpp:ConstBufferSequence[]—a single buffer or a range of them—and the archetype stands in for that universally-quantified requirement. diff --git a/doc/modules/ROOT/pages/9.design/9k.Executor.adoc b/doc/modules/ROOT/pages/9.design/9k.Executor.adoc index 429313bb3..00644ddc5 100644 --- a/doc/modules/ROOT/pages/9.design/9k.Executor.adoc +++ b/doc/modules/ROOT/pages/9.design/9k.Executor.adoc @@ -9,7 +9,7 @@ The cpp:Executor[] concept exists to answer one question: when a coroutine is re [source,cpp] ---- -include::example$snippets/9k_executor.cpp[tag=executor_concept] +include::example$include/boost/capy/concept/executor.hpp[tag=executor_concept] ---- An cpp:Executor[] provides exactly two scheduling operations: diff --git a/doc/modules/ROOT/pages/9.design/9n.WhyNotCobaltConcepts.adoc b/doc/modules/ROOT/pages/9.design/9n.WhyNotCobaltConcepts.adoc index 7ec33d334..c876e8c85 100644 --- a/doc/modules/ROOT/pages/9.design/9n.WhyNotCobaltConcepts.adoc +++ b/doc/modules/ROOT/pages/9.design/9n.WhyNotCobaltConcepts.adoc @@ -41,7 +41,7 @@ cpp:IoRunnable[] refines cpp:IoAwaitable[] with operations needed to start a tas [source,cpp] ---- -include::example$snippets/9n_why_not_cobalt_concepts.cpp[tag=io_runnable_concept] +include::example$include/boost/capy/concept/io_runnable.hpp[tag=io_runnable_concept] ---- Context injection (cpp:IoRunnable::set_environment[set_environment], cpp:IoRunnable::set_continuation[set_continuation]) consists of `noexcept` requirements that cpp:IoRunnable[] places on `T::promise_type`; launcher functions invoke them through the typed handle returned by cpp:IoRunnable::handle[handle()] before resuming the frame. @@ -215,7 +215,7 @@ Capy's cpp:WriteStream[] concept requires `write_some` to accept any cpp:ConstBu [source,cpp] ---- -include::example$snippets/9n_why_not_cobalt_concepts.cpp[tag=write_stream_concept_short] +include::example$include/boost/capy/concept/write_stream.hpp[tag=write_stream_concept] ---- The type-erased wrapper cpp:any_write_stream[] also models the cpp:WriteStream[] concept. Its `write_some` is a template that accepts any cpp:ConstBufferSequence[]: @@ -438,7 +438,7 @@ Capy separates the abstraction from the wrapper. cpp:WriteStream[] is a {cpp}20 [source,cpp] ---- -include::example$snippets/9n_why_not_cobalt_concepts.cpp[tag=write_stream_concept] +include::example$include/boost/capy/concept/write_stream.hpp[tag=write_stream_concept] ---- cpp:any_write_stream[] is a type-erased wrapper that satisfies this concept. It is one possible reification, not the only one. Users can: diff --git a/include/boost/capy/buffers.hpp b/include/boost/capy/buffers.hpp index 8c5385b8c..9185d9934 100644 --- a/include/boost/capy/buffers.hpp +++ b/include/boost/capy/buffers.hpp @@ -235,11 +235,13 @@ class const_buffer @see const_buffer, MutableBufferSequence */ +// tag::const_buffer_sequence_concept[] template concept ConstBufferSequence = std::is_convertible_v || ( std::ranges::bidirectional_range && std::is_convertible_v, const_buffer>); +// end::const_buffer_sequence_concept[] /** Requires a type to convert to `mutable_buffer`, or be a range of such buffers. @@ -258,11 +260,13 @@ concept ConstBufferSequence = @see mutable_buffer, ConstBufferSequence */ +// tag::mutable_buffer_sequence_concept[] template concept MutableBufferSequence = std::is_convertible_v || ( std::ranges::bidirectional_range && std::is_convertible_v, mutable_buffer>); +// end::mutable_buffer_sequence_concept[] /** Return an iterator to the first buffer in a sequence. diff --git a/include/boost/capy/concept/executor.hpp b/include/boost/capy/concept/executor.hpp index 21e4cc4bd..adf9cd385 100644 --- a/include/boost/capy/concept/executor.hpp +++ b/include/boost/capy/concept/executor.hpp @@ -140,6 +140,7 @@ class execution_context; @see ExecutionContext, execution_context */ +// tag::executor_concept[] template concept Executor = std::is_nothrow_copy_constructible_v && @@ -157,6 +158,7 @@ concept Executor = { ce.dispatch(c) } -> std::same_as>; { ce.post(c) }; }; +// end::executor_concept[] } // capy } // boost diff --git a/include/boost/capy/concept/io_runnable.hpp b/include/boost/capy/concept/io_runnable.hpp index 661847b70..acc9a47bc 100644 --- a/include/boost/capy/concept/io_runnable.hpp +++ b/include/boost/capy/concept/io_runnable.hpp @@ -86,6 +86,7 @@ namespace capy { @see IoAwaitable, run, run_async */ +// tag::io_runnable_concept[] template concept IoRunnable = IoAwaitable && @@ -102,6 +103,7 @@ concept IoRunnable = requires(typename T::promise_type& p) { p.result(); }); +// end::io_runnable_concept[] } // namespace capy } // namespace boost diff --git a/include/boost/capy/concept/read_stream.hpp b/include/boost/capy/concept/read_stream.hpp index 1b8b02d70..d2b05f236 100644 --- a/include/boost/capy/concept/read_stream.hpp +++ b/include/boost/capy/concept/read_stream.hpp @@ -104,6 +104,7 @@ namespace capy { @see IoAwaitable, MutableBufferSequence, awaitable_decomposes_to */ +// tag::read_stream_concept[] template concept ReadStream = requires(T& stream, mutable_buffer_archetype buffers) @@ -113,6 +114,7 @@ concept ReadStream = decltype(stream.read_some(buffers)), std::error_code, std::size_t>; }; +// end::read_stream_concept[] } // namespace capy } // namespace boost diff --git a/include/boost/capy/concept/write_stream.hpp b/include/boost/capy/concept/write_stream.hpp index 97b9554b0..fc3b3fbcc 100644 --- a/include/boost/capy/concept/write_stream.hpp +++ b/include/boost/capy/concept/write_stream.hpp @@ -113,6 +113,7 @@ namespace capy { @see IoAwaitable, ConstBufferSequence, awaitable_decomposes_to */ +// tag::write_stream_concept[] template concept WriteStream = requires(T& stream, const_buffer_archetype buffers) @@ -122,6 +123,7 @@ concept WriteStream = decltype(stream.write_some(buffers)), std::error_code, std::size_t>; }; +// end::write_stream_concept[] } // namespace capy } // namespace boost diff --git a/include/boost/capy/ex/frame_allocator.hpp b/include/boost/capy/ex/frame_allocator.hpp index b3837515c..2d66c6a26 100644 --- a/include/boost/capy/ex/frame_allocator.hpp +++ b/include/boost/capy/ex/frame_allocator.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -135,6 +136,7 @@ set_current_frame_allocator( @see get_current_frame_allocator, set_current_frame_allocator */ +// tag::safe_resume[] inline void safe_resume(std::coroutine_handle<> h) noexcept { @@ -142,6 +144,7 @@ safe_resume(std::coroutine_handle<> h) noexcept h.resume(); set_current_frame_allocator(saved); } +// end::safe_resume[] } // namespace capy } // namespace boost diff --git a/test/doc/snippets/4d_io_awaitable.cpp b/test/doc/snippets/4d_io_awaitable.cpp index 02f731982..2249a0553 100644 --- a/test/doc/snippets/4d_io_awaitable.cpp +++ b/test/doc/snippets/4d_io_awaitable.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -91,24 +92,7 @@ struct io_awaiter_signature // end::two_arg_await_suspend[] }; -// The real definition lives in ; -// the sketch is checked below against types that satisfy (and fail) -// the real concept. -namespace concept_sketch { - -// tag::io_awaitable_concept[] -template -concept IoAwaitable = - requires(A a, std::coroutine_handle<> h, io_env const* env) { - a.await_suspend(h, env); - }; -// end::io_awaitable_concept[] - -} // namespace concept_sketch - -static_assert(concept_sketch::IoAwaitable>); static_assert(capy::IoAwaitable>); -static_assert(!concept_sketch::IoAwaitable); static_assert(!capy::IoAwaitable); struct caller_promise diff --git a/test/doc/snippets/5c_sequences.cpp b/test/doc/snippets/5c_sequences.cpp index 6c1a90001..ab4ea1a01 100644 --- a/test/doc/snippets/5c_sequences.cpp +++ b/test/doc/snippets/5c_sequences.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -52,7 +53,6 @@ #include #include -#include #include #include #include @@ -68,38 +68,11 @@ namespace { using namespace boost::capy; -// The page's concept definitions; the sketch namespace keeps them from -// clashing with the real ones, and the asserts below prove they match. -namespace concept_sketch { - -// tag::const_buffer_sequence_concept[] -template -concept ConstBufferSequence = - std::is_convertible_v || ( - std::ranges::bidirectional_range && - std::is_convertible_v, const_buffer>); -// end::const_buffer_sequence_concept[] - -// tag::mutable_buffer_sequence_concept[] -template -concept MutableBufferSequence = - std::is_convertible_v || ( - std::ranges::bidirectional_range && - std::is_convertible_v, mutable_buffer>); -// end::mutable_buffer_sequence_concept[] - -} // namespace concept_sketch - -static_assert(concept_sketch::ConstBufferSequence == - ConstBufferSequence); -static_assert(concept_sketch::ConstBufferSequence> == - ConstBufferSequence>); -static_assert(concept_sketch::ConstBufferSequence == - ConstBufferSequence); -static_assert(concept_sketch::MutableBufferSequence == - MutableBufferSequence); -static_assert(concept_sketch::MutableBufferSequence == - MutableBufferSequence); +static_assert(ConstBufferSequence); +static_assert(ConstBufferSequence>); +static_assert(!ConstBufferSequence); +static_assert(MutableBufferSequence); +static_assert(!MutableBufferSequence); // tag::send_signature[] template diff --git a/test/doc/snippets/6b_streams.cpp b/test/doc/snippets/6b_streams.cpp index 75df9c9f3..fa1673433 100644 --- a/test/doc/snippets/6b_streams.cpp +++ b/test/doc/snippets/6b_streams.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -38,8 +39,6 @@ #include #include -#include -#include #include #include // tag::any_read_stream_include[] @@ -72,40 +71,9 @@ namespace { using namespace boost::capy; -namespace definition { - -// tag::read_stream_concept[] -template -concept ReadStream = - requires(T& stream, mutable_buffer_archetype buffers) - { - { stream.read_some(buffers) } -> IoAwaitable; - requires awaitable_decomposes_to< - decltype(stream.read_some(buffers)), - std::error_code, std::size_t>; - }; -// end::read_stream_concept[] - -// tag::write_stream_concept[] -template -concept WriteStream = - requires(T& stream, const_buffer_archetype buffers) - { - { stream.write_some(buffers) } -> IoAwaitable; - requires awaitable_decomposes_to< - decltype(stream.write_some(buffers)), - std::error_code, std::size_t>; - }; -// end::write_stream_concept[] - -// The page's definitions must match the library's. -static_assert(definition::ReadStream); static_assert(capy::ReadStream); -static_assert(definition::WriteStream); static_assert(capy::WriteStream); -} // namespace definition - task<> partial_read(test::stream& stream) { // tag::read_partial[] diff --git a/test/doc/snippets/9c_read_stream.cpp b/test/doc/snippets/9c_read_stream.cpp index 1da770a53..91ccf385f 100644 --- a/test/doc/snippets/9c_read_stream.cpp +++ b/test/doc/snippets/9c_read_stream.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -62,26 +63,8 @@ namespace { using namespace boost::capy; -namespace definition { - -// tag::concept_definition[] -template -concept ReadStream = - requires(T& stream, mutable_buffer_archetype buffers) - { - { stream.read_some(buffers) } -> IoAwaitable; - requires awaitable_decomposes_to< - decltype(stream.read_some(buffers)), - std::error_code, std::size_t>; - }; -// end::concept_definition[] - -// The page's definition must match the library's. -static_assert(definition::ReadStream); static_assert(capy::ReadStream); -} // namespace definition - namespace composed { // tag::read_signature[] diff --git a/test/doc/snippets/9f_write_stream.cpp b/test/doc/snippets/9f_write_stream.cpp index 9b301bdfa..5eade1038 100644 --- a/test/doc/snippets/9f_write_stream.cpp +++ b/test/doc/snippets/9f_write_stream.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -37,7 +38,6 @@ #endif #include -#include #include #include #include @@ -61,27 +61,7 @@ namespace { using namespace boost::capy; -// The real definition lives in ; -// the sketch is checked below against the real concept. -namespace concept_sketch { - -// tag::write_stream_concept[] -template -concept WriteStream = - requires(T& stream, const_buffer_archetype buffers) - { - { stream.write_some(buffers) } -> IoAwaitable; - requires awaitable_decomposes_to< - decltype(stream.write_some(buffers)), - std::error_code, std::size_t>; - }; -// end::write_stream_concept[] - -} // namespace concept_sketch - -static_assert(concept_sketch::WriteStream); static_assert(capy::WriteStream); -static_assert(!concept_sketch::WriteStream); static_assert(!capy::WriteStream); // The real algorithms live in and diff --git a/test/doc/snippets/9k_executor.cpp b/test/doc/snippets/9k_executor.cpp index 92e68f0ff..e903dc2fb 100644 --- a/test/doc/snippets/9k_executor.cpp +++ b/test/doc/snippets/9k_executor.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -40,7 +41,6 @@ #include #include #include -#include #include #include @@ -58,40 +58,9 @@ namespace { using namespace boost::capy; -// The page shows the concept exactly as defined in -// ; compiling a copy keeps the page -// in sync with the real definition. -namespace concept_def { - -// tag::executor_concept[] -template -concept Executor = - std::is_nothrow_copy_constructible_v && - std::is_nothrow_move_constructible_v && - requires(E& e, E const& ce, E const& ce2, - continuation c) - { - { ce == ce2 } noexcept -> std::convertible_to; - { ce.context() } noexcept; - requires std::is_lvalue_reference_v< - decltype(ce.context())> && - std::derived_from< - std::remove_reference_t< - decltype(ce.context())>, - execution_context>; - { ce.on_work_started() } noexcept; - { ce.on_work_finished() } noexcept; - - { ce.dispatch(c) } -> std::same_as>; - { ce.post(c) }; - }; -// end::executor_concept[] - -} // namespace concept_def - -static_assert(concept_def::Executor); -static_assert(concept_def::Executor); -static_assert(!concept_def::Executor); +static_assert(capy::Executor); +static_assert(capy::Executor); +static_assert(!capy::Executor); // Scaffolding context so the conforming dispatch shown on the page // compiles. @@ -215,20 +184,6 @@ struct io_awaitable_sketch } }; -namespace safe_resume_def { - -// tag::safe_resume[] -inline void -safe_resume(std::coroutine_handle<> h) noexcept -{ - auto* saved = get_current_frame_allocator(); - h.resume(); - set_current_frame_allocator(saved); -} -// end::safe_resume[] - -} // namespace safe_resume_def - // Declarations are enough for the concept check; the definitions are // a real implementation's concern. // tag::minimal_executor[] diff --git a/test/doc/snippets/9n_why_not_cobalt_concepts.cpp b/test/doc/snippets/9n_why_not_cobalt_concepts.cpp index c4557b071..f6621ab2a 100644 --- a/test/doc/snippets/9n_why_not_cobalt_concepts.cpp +++ b/test/doc/snippets/9n_why_not_cobalt_concepts.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -37,8 +38,6 @@ #endif #include -#include -#include #include #include #include @@ -63,46 +62,9 @@ namespace { capy::task<> my_algo(capy::any_write_stream& stream); // end::capy_signature[] -// The concept definitions the page reproduces must stay in sync with -// the shipped ones; local mirrors compile them and the static_asserts -// below compare them against the real concepts. -namespace task_requirements { - -using boost::capy::io_env; - -// tag::io_awaitable_concept[] -template -concept IoAwaitable = - requires(A a, std::coroutine_handle<> h, io_env const* env) - { - a.await_suspend(h, env); - }; -// end::io_awaitable_concept[] - -// tag::io_runnable_concept[] -template -concept IoRunnable = - IoAwaitable && - requires { typename T::promise_type; } && - requires(T& t, T const& ct, typename T::promise_type const& cp, - typename T::promise_type& p) - { - { ct.handle() } noexcept - -> std::same_as>; - { cp.exception() } noexcept -> std::same_as; - { t.release() } noexcept; - { p.set_continuation(std::coroutine_handle<>{}) } noexcept; - { p.set_environment(static_cast(nullptr)) } noexcept; - } && - (std::is_void_v().await_resume())> || - requires(typename T::promise_type& p) { p.result(); }); -// end::io_runnable_concept[] - -static_assert(IoAwaitable> == capy::IoAwaitable>); -static_assert(IoRunnable> == capy::IoRunnable>); -static_assert(IoRunnable> == capy::IoRunnable>); - -} // namespace task_requirements +static_assert(capy::IoAwaitable>); +static_assert(capy::IoRunnable>); +static_assert(capy::IoRunnable>); namespace context_propagation { @@ -118,25 +80,6 @@ struct child_operation } // namespace context_propagation -namespace concept_short { - -using boost::capy::const_buffer_archetype; -using boost::capy::IoAwaitable; - -// tag::write_stream_concept_short[] -template -concept WriteStream = - requires(T& stream, const_buffer_archetype buffers) - { - { stream.write_some(buffers) } -> IoAwaitable; - // ... - }; -// end::write_stream_concept_short[] - -static_assert(WriteStream); - -} // namespace concept_short - // tag::semantics_quote[] // From capy/concept/write_stream.hpp @@ -246,28 +189,6 @@ any_write_stream::any_write_stream(S s) } // namespace awaitable_storage -namespace concept_full { - -using boost::capy::awaitable_decomposes_to; -using boost::capy::const_buffer_archetype; -using boost::capy::IoAwaitable; - -// tag::write_stream_concept[] -template -concept WriteStream = - requires(T& stream, const_buffer_archetype buffers) - { - { stream.write_some(buffers) } -> IoAwaitable; - requires awaitable_decomposes_to< - decltype(stream.write_some(buffers)), - std::error_code, std::size_t>; - }; -// end::write_stream_concept[] - -static_assert(WriteStream == - capy::WriteStream); -static_assert(WriteStream); - -} // namespace concept_full +static_assert(capy::WriteStream); } // namespace