Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qcoro for openSUSE:Factory checked in at 2026-02-16 13:23:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qcoro (Old) and /work/SRC/openSUSE:Factory/.qcoro.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qcoro" Mon Feb 16 13:23:01 2026 rev:13 rq:1332990 version:0.13.0 Changes: -------- --- /work/SRC/openSUSE:Factory/qcoro/qcoro.changes 2025-04-07 17:35:06.901053812 +0200 +++ /work/SRC/openSUSE:Factory/.qcoro.new.1977/qcoro.changes 2026-02-16 13:23:03.988414912 +0100 @@ -1,0 +2,18 @@ +Fri Feb 13 21:54:24 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 0.13.0 + * misc doc fixup + * README: Fix fibonacci() demo generator produces infinite zero sequence + * AsyncGenerator: implement await_transform in promise type + * Make generator's .end() method const + * qcoro.h: include network and dbus headers conditionally + * Fix QCoroNetworkReply test on Qt 6.10 + * Use dbus-run-session to run D-Bus tests instead of dbus-launch + * Fix include paths for use with FetchContent + * Fix BUILD_INTERFACE expression + * Fix memory leak in QFuture coro wrapper + * Enable users to construct an invalid GeneratorIterator + * Update documentation link + * Fix typo in reading.md + +------------------------------------------------------------------- Old: ---- qcoro-0.12.0.tar.gz New: ---- qcoro-0.13.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qcoro.spec ++++++ --- /var/tmp/diff_new_pack.1Htid4/_old 2026-02-16 13:23:04.800448692 +0100 +++ /var/tmp/diff_new_pack.1Htid4/_new 2026-02-16 13:23:04.804448859 +0100 @@ -36,7 +36,7 @@ %endif # Name: qcoro%{?_pkg_name_suffix} -Version: 0.12.0 +Version: 0.13.0 Release: 0 Summary: Coroutines for Qt License: MIT ++++++ qcoro-0.12.0.tar.gz -> qcoro-0.13.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/actions/install-qt/action.yml new/qcoro-0.13.0/.github/actions/install-qt/action.yml --- old/qcoro-0.12.0/.github/actions/install-qt/action.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/actions/install-qt/action.yml 2026-02-13 22:28:13.000000000 +0100 @@ -27,7 +27,7 @@ - name: Install Qt shell: bash run: | - pip3 install aqtinstall~=2.1 + pip3 install --break-system-packages aqtinstall~=2.1 case "${{ inputs.platform }}" in "windows") QT_ARCH="win64_msvc2019_64" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/build-docker-images.yml new/qcoro-0.13.0/.github/workflows/build-docker-images.yml --- old/qcoro-0.12.0/.github/workflows/build-docker-images.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/build-docker-images.yml 2026-02-13 22:28:13.000000000 +0100 @@ -24,12 +24,12 @@ matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform linux) - echo "::set-output name=matrix::${matrix_json}" + echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" build: needs: generate-matrix @@ -42,7 +42,7 @@ steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Docker Buildx setup uses: docker/setup-buildx-action@v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/build-linux.yml new/qcoro-0.13.0/.github/workflows/build-linux.yml --- old/qcoro-0.12.0/.github/workflows/build-linux.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/build-linux.yml 2026-02-13 22:28:13.000000000 +0100 @@ -19,9 +19,9 @@ runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: changed_files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v47 name: Get changed files with: files_ignore: | @@ -37,7 +37,7 @@ run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" - echo "::set-output name=source_code_changed::true" + echo "source_code_changed=true" >> "$GITHUB_OUTPUT" generate-matrix: name: Generate build matrix @@ -46,12 +46,12 @@ matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=linux) - echo "::set-output name=matrix::${matrix_json}" + echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" build: needs: [ generate-matrix, detect_run ] @@ -70,7 +70,7 @@ steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} @@ -113,7 +113,7 @@ - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | @@ -121,7 +121,7 @@ - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** @@ -131,7 +131,7 @@ runs-on: ubuntu-latest steps: - name: Upload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Event File path: ${{ github.event_path }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/build-macos.yml new/qcoro-0.13.0/.github/workflows/build-macos.yml --- old/qcoro-0.12.0/.github/workflows/build-macos.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/build-macos.yml 2026-02-13 22:28:13.000000000 +0100 @@ -19,9 +19,9 @@ runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: changed_files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v47 name: Get changed files with: files_ignore: | @@ -37,7 +37,7 @@ run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" - echo "::set-output name=source_code_changed::true" + echo "source_code_changed=true" >> "$GITHUB_OUTPUT" generate-matrix: name: Generate build matrix @@ -46,12 +46,12 @@ matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=macos) - echo "::set-output name=matrix::${matrix_json}" + echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" build: needs: [ generate-matrix, detect_run ] @@ -64,7 +64,7 @@ steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} - name: Install Qt @@ -117,7 +117,7 @@ - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | @@ -125,7 +125,7 @@ - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** @@ -135,7 +135,7 @@ runs-on: ubuntu-latest steps: - name: Upload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Event File path: ${{ github.event_path }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/build-windows.yml new/qcoro-0.13.0/.github/workflows/build-windows.yml --- old/qcoro-0.12.0/.github/workflows/build-windows.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/build-windows.yml 2026-02-13 22:28:13.000000000 +0100 @@ -19,9 +19,9 @@ runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: changed_files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v47 name: Get changed files with: files_ignore: | @@ -37,7 +37,7 @@ run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" - echo '::set-output name=source_code_changed::true' + echo "source_code_changed=true" >> "$GITHUB_OUTPUT" generate-matrix: @@ -47,12 +47,12 @@ matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=windows) - echo "::set-output name=matrix::${matrix_json}" + echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" build: needs: [ generate-matrix, detect_run ] @@ -66,7 +66,7 @@ steps: - name: Checkout sources if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Qt if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} @@ -144,7 +144,7 @@ - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | @@ -152,7 +152,7 @@ - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** @@ -162,7 +162,7 @@ runs-on: ubuntu-latest steps: - name: Upload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Event File path: ${{ github.event_path }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/generate-matrix.py new/qcoro-0.13.0/.github/workflows/generate-matrix.py --- old/qcoro-0.12.0/.github/workflows/generate-matrix.py 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/generate-matrix.py 2026-02-13 22:28:13.000000000 +0100 @@ -64,9 +64,9 @@ if platform == "windows": return "windows-2022" if platform == "linux": - return "ubuntu-20.04" + return "ubuntu-latest" if platform == "macos": - return "macos-13" + return "macos-15" raise RuntimeError(f"Invalid platform '{platform}'.") @@ -104,6 +104,11 @@ for qt_version in qt: for platform in filtered_platforms: + # Skip Qt5 on MacOS - the new runners are based on arm64, for which Qt5 doesn't have binary + # releases + if platform["name"] == "macos" and qt_version["version"].startswith("5"): + continue + for compiler in platform["compilers"]: if "versions" in compiler: for compiler_version in compiler["versions"]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/.github/workflows/update-docs.yml new/qcoro-0.13.0/.github/workflows/update-docs.yml --- old/qcoro-0.12.0/.github/workflows/update-docs.yml 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/.github/workflows/update-docs.yml 2026-02-13 22:28:13.000000000 +0100 @@ -14,9 +14,9 @@ runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - id: changed_files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v47 name: Get changed files with: files: | @@ -31,7 +31,7 @@ run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" - echo "::set-output name=docs_changed::true" + echo "docs_changed=true" >> "$GITHUB_OUTPUT" build: @@ -40,9 +40,9 @@ needs: detect_run if: ${{ needs.detect_run.outputs.docs_changed == 'true' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install dependencies @@ -64,7 +64,7 @@ })();</script><!-- End Matomo Code -->{% endblock %}" > docs/overrides/main.html mkdocs build --verbose - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: site path: site @@ -79,7 +79,7 @@ needs: build steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: site path: site diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/CMakeLists.txt new/qcoro-0.13.0/CMakeLists.txt --- old/qcoro-0.12.0/CMakeLists.txt 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/CMakeLists.txt 2026-02-13 22:28:13.000000000 +0100 @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.18.4) -set(qcoro_VERSION 0.12.0) +set(qcoro_VERSION 0.13.0) set(qcoro_SOVERSION 0) project(qcoro LANGUAGES CXX VERSION ${qcoro_VERSION}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/README.md new/qcoro-0.13.0/README.md --- old/qcoro-0.12.0/README.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/README.md 2026-02-13 22:28:13.000000000 +0100 @@ -38,7 +38,7 @@ Additionally, there's a magical `qCoro()` function that can wrap many native Qt functions and types to make them coroutine-friendly. -Go check the [documentation](https://qcoro.dev/reference) for a full list of all supported +Go check the [documentation](https://qcoro.dev/reference/coro) for a full list of all supported features and Qt types. ### `QDBusPendingCall` @@ -172,13 +172,11 @@ with existing algorithms. ```cpp -QCoro::Generator<int> fibonacci() { - quint64 a = 0, b = 0; +QCoro::Generator<quint64> fibonacci() { + quint64 a = 0, b = 1; Q_FOREVER { - co_yield b; - const auto tmp = b; - a = b; - b += tmp; + co_yield a; + a = std::exchange(b, a + b); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/cmake/AddQCoroLibrary.cmake new/qcoro-0.13.0/cmake/AddQCoroLibrary.cmake --- old/qcoro-0.12.0/cmake/AddQCoroLibrary.cmake 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/cmake/AddQCoroLibrary.cmake 2026-02-13 22:28:13.000000000 +0100 @@ -124,6 +124,7 @@ ${target_include_interface} $<BUILD_INTERFACE:${QCORO_SOURCE_DIR}/qcoro> ${target_include_interface} $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> ${target_include_interface} $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> + ${target_include_interface} $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/QCoro> ${target_include_interface} $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ${target_include_interface} $<INSTALL_INTERFACE:${QCORO_INSTALL_INCLUDEDIR}> ${target_include_interface} $<INSTALL_INTERFACE:${QCORO_INSTALL_INCLUDEDIR}/qcoro> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/cmake/GenerateHeaders.cmake new/qcoro-0.13.0/cmake/GenerateHeaders.cmake --- old/qcoro-0.12.0/cmake/GenerateHeaders.cmake 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/cmake/GenerateHeaders.cmake 2026-02-13 22:28:13.000000000 +0100 @@ -1,6 +1,6 @@ function(generate_headers output_var) set(options) - set(oneValueArgs OUTPUT_DIR ORIGINAL_PREFIX ORIGINAL_HEADERS_VAR) + set(oneValueArgs OUTPUT_DIR ORIGINAL_PREFIX ORIGINAL_HEADERS_VAR SOURCE_DIR) set(multiValueArgs HEADER_NAMES) cmake_parse_arguments(GH "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -8,9 +8,13 @@ set(GH_OUTPUT_DIR "${GH_OUTPUT_DIR}/") endif() + if (NOT GH_SOURCE_DIR) + set(GH_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + foreach(_headername ${GH_HEADER_NAMES}) string(TOLOWER "${_headername}" originalbase) - set(CC_ORIGINAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${GH_ORIGINAL_PREFIX}/${originalbase}.h") + set(CC_ORIGINAL_FILE "${GH_SOURCE_DIR}/${GH_ORIGINAL_PREFIX}/${originalbase}.h") if (NOT EXISTS ${CC_ORIGINAL_FILE}) message(FATAL_ERROR "Could not find header \"${CC_ORIGINAL_FILE}\"") endif() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/changelog.md new/qcoro-0.13.0/docs/changelog.md --- old/qcoro-0.12.0/docs/changelog.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/changelog.md 2026-02-13 22:28:13.000000000 +0100 @@ -10,6 +10,11 @@ # Changelog +## 0.12.0 (2025-04-03) + +* [Release announcement](news/2025/2025-04-03-qcoro-0.12.0-announcement.md) +* [Github release changelog](https://github.com/qcoro/qcoro/releases/tag/v0.12.0) + ## 0.11.0 (2024-10-04) * [Release announcement](news/2024/2024-10-04-qcoro-0.11.0-announcement.md) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/coroutines/coawait.md new/qcoro-0.13.0/docs/coroutines/coawait.md --- old/qcoro-0.12.0/docs/coroutines/coawait.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/coroutines/coawait.md 2026-02-13 22:28:13.000000000 +0100 @@ -6,7 +6,7 @@ # `co_await` Explained -The following paragraphs try to explain what is a coroutine and what `co_await` does in some +The following paragraphs try to explain what a coroutine is and what `co_await` does in some simple way. I don't guarantee that any of this is factically correct. For more gritty (and correct) details, refer to the articles linked at the bottom of this document. @@ -24,10 +24,10 @@ document. Now let's look at the `co_await` keyword. This keyword tells the compiler that this is the point where -the coroutine wants to be suspended, until the *awaited* object (the *awaitable*) is ready. Anything type +the coroutine wants to be suspended, until the *awaited* object (the *awaitable*) is ready. Any type can be *awaitable* - either because it directly implements the interface needed by the C++ coroutine machinery, or because some external tools (like this library) are provided to wrap that type into something -that implements the *awaitable* interface. +that implements the *awaitable* interface. For example: `QCoro::Task<Foo>`. The C++ coroutines introduce two additional keywords -`co_return` and `co_yield`: @@ -36,7 +36,4 @@ which is likely why there's a special keyword for returning from coroutines. `co_yield` allows a coroutine to produce a result without actually returning. Can be used for writing -generators. Currently, this library has no support/usage of `co_yield`, so I won't go into more details -here. - - +generators. See the sections on `QCoro::Generator<T>` and `QCoro::AsyncGenerator<T>` for more information. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/coroutines/reading.md new/qcoro-0.13.0/docs/coroutines/reading.md --- old/qcoro-0.12.0/docs/coroutines/reading.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/coroutines/reading.md 2026-02-13 22:28:13.000000000 +0100 @@ -14,7 +14,7 @@ * [Understanding the promise type](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type) * [Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer) -I can also recommend numerous articles about C++ coroutines by [Raymond Chen on his blog OldNewThink][oldnewthing]. +I can also recommend numerous articles about C++ coroutines by [Raymond Chen on his blog OldNewThing][oldnewthing]. [oldnewthing]: https://devblogs.microsoft.com/oldnewthing/author/oldnewthing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/examples/qnetworkreply.cpp new/qcoro-0.13.0/docs/examples/qnetworkreply.cpp --- old/qcoro-0.12.0/docs/examples/qnetworkreply.cpp 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/examples/qnetworkreply.cpp 2026-02-13 22:28:13.000000000 +0100 @@ -8,8 +8,8 @@ auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")}); // When the reply finishes, the coroutine is resumed and we can access the reply content. const auto data = reply->readAll(); - // Raise your hand if you never forgot to delete a QNetworkReply... - delete reply; + // Raise your hand if you ever forgot to delete a QNetworkReply... + reply->deleteLater(); doSomethingWithData(data); // Extra bonus: the QNetworkAccessManager is destroyed automatically, since it's on stack. } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/examples/qprocess.cpp new/qcoro-0.13.0/docs/examples/qprocess.cpp --- old/qcoro-0.12.0/docs/examples/qprocess.cpp 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/examples/qprocess.cpp 2026-02-13 22:28:13.000000000 +0100 @@ -5,7 +5,7 @@ auto process = qCoro(basicProcess); qDebug() << "Starting ls..."; co_await process.start(QStringLiteral("/bin/ls"), {dirPath}); - qDebug() << "Ls started, reading directory..."; + qDebug() << "ls started, reading directory..."; co_await process.waitForFinished(); qDebug() << "Done"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/news/2026/2026-02-13-qcoro-0.13.0-announcement.md new/qcoro-0.13.0/docs/news/2026/2026-02-13-qcoro-0.13.0-announcement.md --- old/qcoro-0.12.0/docs/news/2026/2026-02-13-qcoro-0.13.0-announcement.md 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.13.0/docs/news/2026/2026-02-13-qcoro-0.13.0-announcement.md 2026-02-13 22:28:13.000000000 +0100 @@ -0,0 +1,82 @@ +--- +title: QCoro 0.13.0 Release Announcement +date: "2026-02-13" +description: > + AsyncGenerator improvements, build fixes and bugfixes +--- + +<!-- +SPDX-FileCopyrightText: 2026 Daniel Vrátil <[email protected]> + +SPDX-License-Identifier: GFDL-1.3-or-later +--> + +# QCoro 0.13.0 Release Announcement + +This release brings improvements to generators, better build system integration and +several bugfixes. + +As always, big thanks to everyone who reported issues and contributed to QCoro. +Your help is much appreciated! + +## Directly Awaiting Qt Types in AsyncGenerator Coroutines + +The biggest improvement in this release is that `QCoro::AsyncGenerator` coroutines now support +directly `co_await`ing Qt types without the `qCoro()` wrapper, just like `QCoro::Task` coroutines +already do ([#292][issue292]). + +Previously, if you wanted to await a `QNetworkReply` inside an `AsyncGenerator`, you had to +wrap it with `qCoro()`: + +```cpp +QCoro::AsyncGenerator<QByteArray> fetchPages(QNetworkAccessManager &nam, QStringList urls) { + for (const auto &url : urls) { + auto *reply = co_await qCoro(nam.get(QNetworkRequest{QUrl{url}})); + co_yield reply->readAll(); + } +} +``` + +Starting with QCoro 0.13.0, you can `co_await` directly, just like in `QCoro::Task`: + +```cpp +QCoro::AsyncGenerator<QByteArray> fetchPages(QNetworkAccessManager &nam, QStringList urls) { + for (const auto &url : urls) { + auto *reply = co_await nam.get(QNetworkRequest{QUrl{url}}); + co_yield reply->readAll(); + } +} +``` + +## Other Features and Changes + +* Generator's `.end()` method is now `const` (and `constexpr`), so it can be called on const generator objects ([#294][issue294]). +* `GeneratorIterator` can now be constructed in an invalid state, allowing lazy initialization of iterators ([#318][issue318]). +* `qcoro.h` now only includes QtNetwork and QtDBus headers when those features are actually enabled, resulting in cleaner builds when optional modules are disabled ([#280][issue280]). + +## Bugfixes + +* Fixed memory leak in QFuture coro wrapper when a task is destroyed while awaiting on a QFuture ([#312][issue312], Daniel Vrátil) +* Fixed include paths when using QCoro with CMake's FetchContent ([#282][issue282], Daniel Vrátil; [#310][pr310], Nicolas Fella) +* Fixed QCoroNetworkReply test on Qt 6.10 ([#305][pr305], Daniel Vrátil) + +## Full changelog + +[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.13.0) + +## Support + +If you enjoy using QCoro, consider supporting its development on [GitHub Sponsors][github-sponsors] or buy me a coffee +on [Ko-fi][kofi] (after all, more coffee means more code, right?). + +[issue292]: https://github.com/qcoro/qcoro/issues/292 +[issue294]: https://github.com/qcoro/qcoro/issues/294 +[issue318]: https://github.com/qcoro/qcoro/issues/318 +[issue280]: https://github.com/qcoro/qcoro/issues/280 +[issue312]: https://github.com/qcoro/qcoro/issues/312 +[issue282]: https://github.com/qcoro/qcoro/issues/282 +[pr310]: https://github.com/qcoro/qcoro/pull/310 +[pr305]: https://github.com/qcoro/qcoro/pull/305 + +[github-sponsors]: https://github.com/sponsors/danvratil +[kofi]: https://ko-fi.com/danvratil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/core/qiodevice.md new/qcoro-0.13.0/docs/reference/core/qiodevice.md --- old/qcoro-0.12.0/docs/reference/core/qiodevice.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/core/qiodevice.md 2026-02-13 22:28:13.000000000 +0100 @@ -18,7 +18,7 @@ ``` [`QIODevice`][qtdoc-qiodevice] has several different IO operations that can be waited on -asynchronously. Since `QIODevice` itself doesn't provide the abaility to `co_await` those +asynchronously. Since `QIODevice` itself doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper class called `QCoroIODevice`. To wrap a `QIODevice` into a `QCoroIODevice`, use [`qCoro()`][qcoro-coro]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/core/qprocess.md new/qcoro-0.13.0/docs/reference/core/qprocess.md --- old/qcoro-0.12.0/docs/reference/core/qprocess.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/core/qprocess.md 2026-02-13 22:28:13.000000000 +0100 @@ -18,7 +18,7 @@ QCoroProcess qCoro(QProcess *); ``` -Same as `QProcess` is a subclass of `QIODevice`, `QCoroProcess` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], +Just as `QProcess` is a subclass of `QIODevice`, `QCoroProcess` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected QIODevice functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. @@ -54,7 +54,7 @@ as well. See the documentation for [`QProcess::start()`][qtdoc-qprocess-start] and -[`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details.o +[`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details. Returns `true` when the process has successfully started, `false` otherwise. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/core/qthread.md new/qcoro-0.13.0/docs/reference/core/qthread.md --- old/qcoro-0.12.0/docs/reference/core/qthread.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/core/qthread.md 2026-02-13 22:28:13.000000000 +0100 @@ -36,8 +36,8 @@ ## `waitForFinished()` -Waits for the Waits for the process to finish or until it times out. Returns `bool` indicating -whether the process has finished successfuly (`true`) or timed out (`false`). +Waits for the thread to finish or until it times out. Returns `bool` indicating +whether the thread has finished successfuly (`true`) or timed out (`false`). thread to finish. Returns `true` if the thread has already finished or if it finishes within the specified timeout. If the thread has not started yet or fails to stop within the specified timeout the coroutine will return `false`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/core/qtimer.md new/qcoro-0.13.0/docs/reference/core/qtimer.md --- old/qcoro-0.12.0/docs/reference/core/qtimer.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/core/qtimer.md 2026-02-13 22:28:13.000000000 +0100 @@ -15,7 +15,7 @@ ``` The QCoro frameworks allows `co_await`ing on [QTimer][qdoc-qtimer] object. The -co-awaiting coroutine is suspended, until the timer finishes, that is until +co-awaiting coroutine is suspended until the timer finishes, that is until [`QTimer::timeout()`][qdoc-qtimer-timeout] signal is emitted. The timer must be active. If the timer is not active (not started yet or already diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/core/signals.md new/qcoro-0.13.0/docs/reference/core/signals.md --- old/qcoro-0.12.0/docs/reference/core/signals.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/core/signals.md 2026-02-13 22:28:13.000000000 +0100 @@ -83,7 +83,7 @@ } ``` -Alternatively, it's possible to use `QCORO_FOREACH` to look over the generator: +Alternatively, it's possible to use `QCORO_FOREACH` to loop over the generator: ```cpp QCORO_FOREACH(const auto [received, total], qCoroSignalListener(reply, &QNetworkReply::downloadProgress)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/coro/generator.md new/qcoro-0.13.0/docs/reference/coro/generator.md --- old/qcoro-0.12.0/docs/reference/coro/generator.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/coro/generator.md 2026-02-13 22:28:13.000000000 +0100 @@ -15,11 +15,11 @@ Generator is a coroutine that that yields a single value and then suspends itself, until resumed again. Then it yields another value and suspends again. Generator can be inifinite (the coroutine will -never finish and will produce new values for ever until destroyed -from the outside) or finite (after generating some amount of values +never finish and will produce new values forever until destroyed +from the outside) or finite (after generating some amount of values, the coroutine finishes). -The `QCoro::Generator<T>` is a template class providing interface +The `QCoro::Generator<T>` is a template class providing an interface for the generator consumer. There is no standard API for generators specified in the C++ standard (as of C++20). The design chosen by QCoro copies the design of cppcoro library, which is for the `Generator<T>` @@ -32,7 +32,7 @@ QCoro::Generator<int> iota(int count) { for (int i = 0; i < count; ++i) { // Yields a value and suspends the generator. - co_yield count; + co_yield i; } } @@ -45,17 +45,17 @@ // Obtains a past-the-end iterator. const auto end = generator.end(); - // Loops until the generator doesn't finish. + // Loops until the generator finishes. while (it != end) { - // Resumes the generator until it co_yields another value. - ++it; // Reads the current value from the iterator. std::cout << *it << std::endl; + // Resumes the generator until it co_yields another value. + ++it; } // The code above can be written more consisely using ranged based for-loop for (const auto value : iota(10)) { - std::count << value << std::endl; + std::cout << value << std::endl; } } ``` @@ -94,10 +94,10 @@ ## Generator lifetime -The lifetime of the generator coroutine is tight to the lifetime +The lifetime of the generator coroutine is tied to the lifetime of the associated `QCoro::Generator<T>` object. The generator coroutine is destroyed when the associated `QCoro::Generator<T>` -object is destroyed, that includes the stack of the coroutine +object is destroyed, which includes the stack of the coroutine and everything allocated on the stack. It doesn't matter whether the coroutine has already finished or @@ -106,7 +106,7 @@ coroutine and all associated state will be destroyed using the regular rules of stack destruction. -Therefore, it is stafe to allocate values on generator coroutine +Therefore, it is safe to allocate values on generator coroutine stack. However, dynamically allocated memory will not be free'd automatically. Therefore if you need to dynamically allocate memory on heap inside the generator coroutine, you must make @@ -115,7 +115,7 @@ it in `std::unique_ptr` or `QScopeGuard`. !!! info "Memory usage" - Keep in mind, that that generator coroutine will keep occupying + Keep in mind that that generator coroutine will keep occupying memory even when not used until it finishes or until the associated `QCoro::Generator<T>` is destroyed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/coro/task.md new/qcoro-0.13.0/docs/reference/coro/task.md --- old/qcoro-0.12.0/docs/reference/coro/task.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/coro/task.md 2026-02-13 22:28:13.000000000 +0100 @@ -100,7 +100,7 @@ continuation is `co_await`ed. If the result of the continuation is not `co_await`ed, the exception is silently ignored. -If an exception is thrown from the `ThenCallback`, then the exception is either propagated to the nex +If an exception is thrown from the `ThenCallback`, then the exception is either propagated to the next chained `then()` continuation or re-thrown if directly `co_await`ed. If the result is not `co_await`ed and no futher `then()` continuation is chained after the one that has thrown, then the exception is silently ignored. @@ -159,7 +159,7 @@ ```cpp QCoro::Task<int> computeAnswer() { - co_await QCoro::sleepFor(std::chrono::years{7'500'00}); + co_await QCoro::sleepFor(std::chrono::years{7'500'000}); co_return 42; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/network/qabstractsocket.md new/qcoro-0.13.0/docs/reference/network/qabstractsocket.md --- old/qcoro-0.12.0/docs/reference/network/qabstractsocket.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/network/qabstractsocket.md 2026-02-13 22:28:13.000000000 +0100 @@ -23,15 +23,15 @@ QCoroAbstractSocket qCoro(QAbstractSocket *); ``` -Same as `QAbstractSocket` is a subclass of `QIODevice`, `QCoroAbstractSocket` subclasses +Just as `QAbstractSocket` is a subclass of `QIODevice`, `QCoroAbstractSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` -Waits for the socket to connect or until it times out. Returns `bool` indicating whether +Waits for the socket to connect or until it times out. Returns `bool` indicating whether the connection has been established (`true`) or whether the operation has timed out or another -error has occurred (`false`). Returns immediatelly when the socket is already in connected +error has occurred (`false`). Returns immediately when the socket is already in connected state. If the timeout is -1, the operation will never time out. @@ -47,7 +47,7 @@ ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. -Returns immediatelly if the socket is not in connected state. +Returns immediately if the socket is not in connected state. If the timeout is -1, the operation will never time out. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/network/qlocalsocket.md new/qcoro-0.13.0/docs/reference/network/qlocalsocket.md --- old/qcoro-0.12.0/docs/reference/network/qlocalsocket.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/network/qlocalsocket.md 2026-02-13 22:28:13.000000000 +0100 @@ -22,15 +22,15 @@ QCoroLocalSocket qCoro(QLocalSocket *); ``` -Same as `QLocalSocket` is a subclass of `QIODevice`, `QCoroLocalSocket` subclasses +Just as `QLocalSocket` is a subclass of `QIODevice`, `QCoroLocalSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` -Waits for the socket to connect or until it times out. Returns `bool` indicating whether +Waits for the socket to connect or until it times out. Returns `bool` indicating whether the connection has been established (`true`) or whether the operation has timed out or another -error has occurred (`false`). Returns immeditelly if the socket is already in connected +error has occurred (`false`). Returns immediately if the socket is already in connected state. If the timeout is -1, the operation will never time out. @@ -46,7 +46,7 @@ ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. -Returns immediatelly if the socket is not in connected state. +Returns immediately if the socket is not in connected state. If the timeout is -1, the operation will never time out. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/docs/reference/network/qtcpserver.md new/qcoro-0.13.0/docs/reference/network/qtcpserver.md --- old/qcoro-0.12.0/docs/reference/network/qtcpserver.md 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/docs/reference/network/qtcpserver.md 2026-02-13 22:28:13.000000000 +0100 @@ -11,7 +11,7 @@ [`QTcpServer`][qtdoc-qtcpserver] really only has one asynchronous operation worth `co_await`ing, and that's `waitForNewConnection()`. -Since `QTcpServer` doesn't provide the ability to `co_await` those operations, QCoro provides +Since `QTcpServer` doesn't provide the ability to `co_await` that operation, QCoro provides a wrapper class `QCoroTcpServer`. To wrap a `QTcpServer` object into the `QCoroTcpServer` wrapper, use [`qCoro()`][qcoro-coro]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/CMakeLists.txt new/qcoro-0.13.0/qcoro/CMakeLists.txt --- old/qcoro-0.12.0/qcoro/CMakeLists.txt 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/CMakeLists.txt 2026-02-13 22:28:13.000000000 +0100 @@ -14,7 +14,6 @@ NO_CMAKE_CONFIG INCLUDEDIR Coro CAMELCASE_HEADERS - QCoro QCoroAsyncGenerator QCoroFwd QCoroGenerator @@ -24,9 +23,11 @@ concepts_p.h coroutine.h macros_p.h + mixins_p.h waitoperationbase_p.h impl/connect.h impl/lazytask.h + impl/mixins.h impl/task.h impl/taskawaiterbase.h impl/taskbase.h @@ -38,6 +39,20 @@ INTERFACE Core ) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/qcoro.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/qcoro.h" + @ONLY +) + +generate_headers( + qcoro_cc_HEADERS + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}" + HEADER_NAMES QCoro + OUTPUT_DIR QCoro + ORIGINAL_HEADERS_VAR qcoro_lc_HEADERS +) + if (NOT QCORO_DISABLE_DEPRECATED_TASK_H) # Install Task conditionally generate_headers( @@ -46,19 +61,19 @@ OUTPUT_DIR QCoro ORIGINAL_HEADERS_VAR task_lc_HEADERS ) - - install( - FILES ${task_lc_HEADERS} - DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/ - COMPONENT Devel - ) - install( - FILES ${task_cc_HEADERS} - DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/ - COMPONENT Devel - ) endif() +install( + FILES ${qcoro_lc_HEADERS} ${task_lc_HEADERS} + DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/ + COMPONENT Devel +) + +install( + FILES ${qcoro_cc_HEADERS} ${task_cc_HEADERS} + DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/ + COMPONENT Devel +) # The QCoroCoroConfig includes the QCoroMacros.cmake so we can't # use the standard file generated by add_qcoro_library. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/core/qcorofuture.h new/qcoro-0.13.0/qcoro/core/qcorofuture.h --- old/qcoro-0.12.0/qcoro/core/qcorofuture.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/core/qcorofuture.h 2026-02-13 22:28:13.000000000 +0100 @@ -23,7 +23,8 @@ class WaitForFinishedOperationBase { public: explicit WaitForFinishedOperationBase(const QFuture<T_> &future) - : mFuture(future) {} + : mFuture(future) {} + Q_DISABLE_COPY(WaitForFinishedOperationBase) QCORO_DEFAULT_MOVE(WaitForFinishedOperationBase) @@ -32,15 +33,18 @@ } void await_suspend(std::coroutine_handle<> awaitingCoroutine) { - auto *watcher = new QFutureWatcher<T_>(); - QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, awaitingCoroutine]() mutable { - watcher->deleteLater(); - awaitingCoroutine.resume(); - }); + auto *watcher = new QFutureWatcher<T_>(mContext.get()); + QObject::connect( + watcher, &QFutureWatcherBase::finished, + mContext.get(), [awaitingCoroutine]() mutable { + // watcher will get deleted with mContext + awaitingCoroutine.resume(); + }); watcher->setFuture(mFuture); } protected: + std::unique_ptr<QObject> mContext = std::make_unique<QObject>(); QFuture<T_> mFuture; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/dbus/qcorodbuspendingcall.h new/qcoro-0.13.0/qcoro/dbus/qcorodbuspendingcall.h --- old/qcoro-0.12.0/qcoro/dbus/qcorodbuspendingcall.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/dbus/qcorodbuspendingcall.h 2026-02-13 22:28:13.000000000 +0100 @@ -47,7 +47,7 @@ Returns a `QDBusMessage` representing the reply to the call. If the call is already finished or has an error, the coroutine will not suspend and the `co_await` - expression will return immediatelly. + expression will return immediately. It is also possible to just directly use a `QDBusPendingCall` in a `co_await` expression to await its completion: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/dbus/qcorodbuspendingreply.h new/qcoro-0.13.0/qcoro/dbus/qcorodbuspendingreply.h --- old/qcoro-0.12.0/qcoro/dbus/qcorodbuspendingreply.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/dbus/qcorodbuspendingreply.h 2026-02-13 22:28:13.000000000 +0100 @@ -72,7 +72,7 @@ Returns a `QDBusMessage` representing the received reply. If the reply is already finished or an error has occurred the coroutine will not suspend and will return - a result immediatelly. + a result immediately. This is a coroutine-friendly equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/impl/mixins.h new/qcoro-0.13.0/qcoro/impl/mixins.h --- old/qcoro-0.12.0/qcoro/impl/mixins.h 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.13.0/qcoro/impl/mixins.h 2026-02-13 22:28:13.000000000 +0100 @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 Daniel Vrátil <[email protected]> +// +// SPDX-License-Identifier: MIT + +#pragma once + +namespace QCoro::detail { + +template<typename T, typename Awaiter> +inline auto AwaitTransformMixin::await_transform(T &&value) { + return Awaiter{std::forward<T>(value)}; +} + +template<Awaitable T> +inline auto && AwaitTransformMixin::await_transform(T &&awaitable) { + return std::forward<T>(awaitable); +} + +template<Awaitable T> +inline auto &AwaitTransformMixin::await_transform(T &awaitable) { + return awaitable; +} + +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/impl/taskpromisebase.h new/qcoro-0.13.0/qcoro/impl/taskpromisebase.h --- old/qcoro-0.12.0/qcoro/impl/taskpromisebase.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/impl/taskpromisebase.h 2026-02-13 22:28:13.000000000 +0100 @@ -27,21 +27,6 @@ return TaskFinalSuspend{mAwaitingCoroutines}; } -template<typename T, typename Awaiter> -inline auto TaskPromiseBase::await_transform(T &&value) { - return Awaiter{std::forward<T>(value)}; -} - -template<Awaitable T> -inline auto && TaskPromiseBase::await_transform(T &&awaitable) { - return std::forward<T>(awaitable); -} - -template<Awaitable T> -inline auto &TaskPromiseBase::await_transform(T &awaitable) { - return awaitable; -} - inline void TaskPromiseBase::addAwaitingCoroutine(std::coroutine_handle<> awaitingCoroutine) { mAwaitingCoroutines.push_back(awaitingCoroutine); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/mixins_p.h new/qcoro-0.13.0/qcoro/mixins_p.h --- old/qcoro-0.12.0/qcoro/mixins_p.h 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.13.0/qcoro/mixins_p.h 2026-02-13 22:28:13.000000000 +0100 @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 Daniel Vrátil <[email protected]> +// +// SPDX-License-Identifier: MIT + +#pragma once + +namespace QCoro::detail { + +template<typename T> +struct awaiter_type; + +template<typename T> +using awaiter_type_t = typename awaiter_type<T>::type; + +class AwaitTransformMixin { +public: + //! Called by `co_await` to obtain an Awaitable for type \c T. + /*! + * When co_awaiting on a value of type \c T, the type \c T must an Awaitable. To allow + * to co_await even on types that are not Awaitable (e.g. 3rd party types like QNetworkReply), + * C++ allows promise_type to provide \c await_transform() function that can transform + * the type \c T into an Awaitable. This is a very powerful mechanism in C++ coroutines. + * + * For types \c T for which there is no valid await_transform() overload, the C++ attempts + * to use those types directly as Awaitables. This is a perfectly valid scenario in cases + * when co_awaiting a type that implements the neccessary Awaitable interface. + * + * In our implementation, the await_transform() is overloaded only for Qt types for which + * a specialiation of the \c QCoro::detail::awaiter_type template class exists. The + * specialization returns type of the Awaiter for the given type \c T. + */ + template<typename T, typename Awaiter = QCoro::detail::awaiter_type_t<std::remove_cvref_t<T>>> + auto await_transform(T &&value); + + //! If the type T is already an awaitable (including Task or LazyTask), then just forward it as it is. + template<Awaitable T> + auto && await_transform(T &&awaitable); + + //! \copydoc template<Awaitable T> QCoro::TaskPromiseBase::await_transform(T &&) + template<Awaitable T> + auto &await_transform(T &awaitable); +}; + +} // namespace QCoro::detail + +#include "impl/mixins.h" \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/qcoro.h new/qcoro-0.13.0/qcoro/qcoro.h --- old/qcoro-0.12.0/qcoro/qcoro.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/qcoro.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Daniel Vrátil <[email protected]> -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "task.h" -#include "qcorocore.h" -#include "qcorodbus.h" -#include "qcoronetwork.h" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/qcoro.h.in new/qcoro-0.13.0/qcoro/qcoro.h.in --- old/qcoro-0.12.0/qcoro/qcoro.h.in 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.13.0/qcoro/qcoro.h.in 2026-02-13 22:28:13.000000000 +0100 @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2021 Daniel Vrátil <[email protected]> +// +// SPDX-License-Identifier: MIT + +#pragma once + +#cmakedefine QCORO_WITH_QTDBUS +#cmakedefine QCORO_WITH_QTNETWORK + +#include "task.h" +#include "qcorocore.h" + +#if defined(QCORO_WITH_QTDBUS) +#include "qcorodbus.h" +#endif + +#if defined(QCORO_WITH_QTNETWORK) +#include "qcoronetwork.h" +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/qcoroasyncgenerator.h new/qcoro-0.13.0/qcoro/qcoroasyncgenerator.h --- old/qcoro-0.12.0/qcoro/qcoroasyncgenerator.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/qcoroasyncgenerator.h 2026-02-13 22:28:13.000000000 +0100 @@ -8,6 +8,7 @@ #pragma once #include "coroutine.h" +#include "mixins_p.h" #include <iterator> #include <exception> @@ -25,7 +26,7 @@ class AsyncGeneratorYieldOperation; class AsyncGeneratorAdvanceOperation; -class AsyncGeneratorPromiseBase { +class AsyncGeneratorPromiseBase : public AwaitTransformMixin { public: AsyncGeneratorPromiseBase() noexcept = default; AsyncGeneratorPromiseBase(const AsyncGeneratorPromiseBase &) = delete; @@ -102,7 +103,8 @@ class IteratorAwaitableBase { protected: - explicit IteratorAwaitableBase(std::nullptr_t) noexcept {} + constexpr explicit IteratorAwaitableBase(std::nullptr_t) noexcept {} + IteratorAwaitableBase( AsyncGeneratorPromiseBase &promise, std::coroutine_handle<> producerCoroutine) noexcept @@ -202,7 +204,7 @@ using reference = std::add_lvalue_reference_t<T>; using pointer = std::add_pointer_t<value_type>; - explicit AsyncGeneratorIterator(std::nullptr_t) noexcept {} + explicit constexpr AsyncGeneratorIterator(std::nullptr_t) noexcept {} explicit AsyncGeneratorIterator(handle_type coroutine) noexcept : m_coroutine(coroutine) {} @@ -353,7 +355,7 @@ } /// Returns an iterator representing the finished generator. - auto end() noexcept { + constexpr iterator end() const noexcept { return iterator{nullptr}; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/qcorogenerator.h new/qcoro-0.13.0/qcoro/qcorogenerator.h --- old/qcoro-0.12.0/qcoro/qcorogenerator.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/qcorogenerator.h 2026-02-13 22:28:13.000000000 +0100 @@ -156,6 +156,11 @@ using pointer = std::add_pointer_t<value_type>; /** + * @brief Constructs an invalid iterator. + **/ + explicit constexpr GeneratorIterator(std::nullptr_t) noexcept {} + + /** * @brief Resumes the generator coroutine until it yields new value or finishes. * * Returns an iterator holding the next value produced by the generator coroutine @@ -197,10 +202,6 @@ friend class QCoro::Generator<T>; /** - * @brief Constructs an invalid iterator. - **/ - explicit GeneratorIterator(std::nullptr_t) {} - /** * @brief Constructs an iterator associated with the given generator coroutine. **/ explicit GeneratorIterator(std::coroutine_handle<promise_type> generatorCoroutine) @@ -261,7 +262,7 @@ /** * @brief Returns iterator "pointing" to the first value produced by the generator. * - * If the generator coroutine did not produce any value and finished immediatelly, + * If the generator coroutine did not produce any value and finished immediately, * the returned iterator will be equal to end(). * * If the generator coroutine has thrown an exception if will be rethrown from here. @@ -281,7 +282,7 @@ * Can be used to check whether the generator have produced another value or * whether it has finished. **/ - iterator end() { + constexpr iterator end() const noexcept { return iterator{nullptr}; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/qcoro/qcorotask.h new/qcoro-0.13.0/qcoro/qcorotask.h --- old/qcoro-0.12.0/qcoro/qcorotask.h 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/qcoro/qcorotask.h 2026-02-13 22:28:13.000000000 +0100 @@ -6,6 +6,7 @@ #include "coroutine.h" #include "concepts_p.h" +#include "mixins_p.h" #include <atomic> #include <exception> @@ -23,12 +24,6 @@ namespace detail { -template<typename T> -struct awaiter_type; - -template<typename T> -using awaiter_type_t = typename awaiter_type<T>::type; - //! Continuation that resumes a coroutine co_awaiting on currently finished coroutine. class TaskFinalSuspend { public: @@ -111,7 +106,7 @@ * * await_transform() - this one is optional and is used by co_awaits inside the coroutine. * It allows the promise to transform the co_awaited type to an Awaitable. */ -class TaskPromiseBase { +class TaskPromiseBase: public AwaitTransformMixin { public: //! Called when the coroutine is started to decide whether it should be suspended or not. /*! @@ -128,32 +123,6 @@ */ auto final_suspend() const noexcept; - //! Called by co_await to obtain an Awaitable for type \c T. - /*! - * When co_awaiting on a value of type \c T, the type \c T must an Awaitable. To allow - * to co_await even on types that are not Awaitable (e.g. 3rd party types like QNetworkReply), - * C++ allows promise_type to provide \c await_transform() function that can transform - * the type \c T into an Awaitable. This is a very powerful mechanism in C++ coroutines. - * - * For types \c T for which there is no valid await_transform() overload, the C++ attempts - * to use those types directly as Awaitables. This is a perfectly valid scenario in cases - * when co_awaiting a type that implements the neccessary Awaitable interface. - * - * In our implementation, the await_transform() is overloaded only for Qt types for which - * a specialiation of the \c QCoro::detail::awaiter_type template class exists. The - * specialization returns type of the Awaiter for the given type \c T. - */ - template<typename T, typename Awaiter = QCoro::detail::awaiter_type_t<std::remove_cvref_t<T>>> - auto await_transform(T &&value); - - //! If the type T is already an awaitable (including Task or LazyTask), then just forward it as it is. - template<Awaitable T> - auto && await_transform(T &&awaitable); - - //! \copydoc template<Awaitable T> QCoro::TaskPromiseBase::await_transform(T &&) - template<Awaitable T> - auto &await_transform(T &awaitable); - //! Called by \c TaskAwaiter when co_awaited. /*! * This function is called by a TaskAwaiter, e.g. an object obtain by co_await diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/requirements.txt new/qcoro-0.13.0/requirements.txt --- old/qcoro-0.12.0/requirements.txt 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/requirements.txt 2026-02-13 22:28:13.000000000 +0100 @@ -1,6 +1,6 @@ setuptools wheel -pymdown-extensions~=10.11 +pymdown-extensions~=10.20 pygments~=2.19 mkdocs mkdocs-material diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/tests/CMakeLists.txt new/qcoro-0.13.0/tests/CMakeLists.txt --- old/qcoro-0.12.0/tests/CMakeLists.txt 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/tests/CMakeLists.txt 2026-02-13 22:28:13.000000000 +0100 @@ -64,9 +64,10 @@ target_compile_definitions(test-${_name} PRIVATE TESTDBUSSERVER_EXECUTABLE=\"$<TARGET_FILE:testdbusserver>\") if (APPLE) # On MacOS dbus-launch doesn't work, so we rely on the session dbus running + # TODO: check whether dbus-run-session works add_test(NAME test-${_name} COMMAND test-${_name}) else() - add_test(NAME test-${_name} COMMAND dbus-launch $<TARGET_FILE:test-${_name}>) + add_test(NAME test-${_name} COMMAND dbus-run-session $<TARGET_FILE:test-${_name}>) endif() _enable_supressions(${_name}) endfunction() @@ -115,7 +116,7 @@ qcoro_add_test(testconstraints) qcoro_add_test(qfuture LINK_LIBRARIES Qt${QT_VERSION_MAJOR}::Concurrent) qcoro_add_test(qcorogenerator) -qcoro_add_test(qcoroasyncgenerator) +qcoro_add_test(qcoroasyncgenerator LINK_LIBRARIES QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Network) qcoro_add_test(qcorowaitfor) if (QCORO_WITH_QTDBUS) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/tests/qcoroasyncgenerator.cpp new/qcoro-0.13.0/tests/qcoroasyncgenerator.cpp --- old/qcoro-0.12.0/tests/qcoroasyncgenerator.cpp 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/tests/qcoroasyncgenerator.cpp 2026-02-13 22:28:13.000000000 +0100 @@ -3,13 +3,17 @@ // SPDX-License-Identifier: MIT #include "qcoroasyncgenerator.h" +#include "network/qcoronetworkreply.h" #include "qcorotimer.h" #include "testobject.h" +#include "testlibs/testhttpserver.h" #include <cstdint> #include <vector> #include <QScopeGuard> +#include <QHostAddress> +#include <QTcpServer> struct Nocopymove { explicit constexpr Nocopymove(int val): val(val) {} @@ -267,6 +271,29 @@ QCORO_VERIFY_EXCEPTION_THROWN(co_await generator.begin(), std::runtime_error); } + QCoro::Task<> testAwaitTransform_coro(QCoro::TestContext) { + TestHttpServer<QTcpServer> server; + server.start(QHostAddress::LocalHost); + QScopeGuard guard([&server]() { + server.stop(); + }); + + const auto createGenerator = [&server]() -> QCoro::AsyncGenerator<QByteArray> { + QNetworkAccessManager nam; + auto *reply = nam.get(QNetworkRequest{QUrl{QStringLiteral("http://127.0.0.1:%1/").arg(server.port())}}); + // This wouldn't compile without await_transform() in AsyncGeneratorPromise. + co_await reply; + + co_yield reply->readAll(); + }; + + auto generator = createGenerator(); + auto iter = co_await generator.begin(); + QCORO_VERIFY(iter != generator.end()); + QCORO_COMPARE(*iter, QByteArray("abcdef")); + QCORO_COMPARE(co_await ++iter, generator.end()); + } + private Q_SLOTS: addTest(Generator) @@ -281,6 +308,7 @@ addTest(ExceptionInDereference) addTest(ExceptionInBegin) addTest(ExceptionInBeginSync) + addTest(AwaitTransform) }; QTEST_GUILESS_MAIN(AsyncGeneratorTest) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/tests/qcoronetworkreply.cpp new/qcoro-0.13.0/tests/qcoronetworkreply.cpp --- old/qcoro-0.12.0/tests/qcoronetworkreply.cpp 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/tests/qcoronetworkreply.cpp 2026-02-13 22:28:13.000000000 +0100 @@ -175,7 +175,8 @@ auto reply = co_await nam.get(request); QCORO_VERIFY(reply != nullptr); QCORO_VERIFY(reply->isFinished()); - QCORO_COMPARE(reply->error(), QNetworkReply::OperationCanceledError); + // Seems to depend on the Qt version which error we get. + QCORO_VERIFY(reply->error() == QNetworkReply::TimeoutError || reply->error() == QNetworkReply::OperationCanceledError); // QNAM is destroyed here and so is all its associated state, which could // crash (or cause invalid memory access) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.12.0/tests/qfuture.cpp new/qcoro-0.13.0/tests/qfuture.cpp --- old/qcoro-0.12.0/tests/qfuture.cpp 2025-04-03 22:44:05.000000000 +0200 +++ new/qcoro-0.13.0/tests/qfuture.cpp 2026-02-13 22:28:13.000000000 +0100 @@ -270,6 +270,36 @@ }(); co_await future; } + +private Q_SLOTS: + // Regression test for #312: verify that destroying a task while it is + // awaiting on a QFuture doesn't cause a crash or memory leak + void testTaskDestroyedBeforeFutureCompletes() { + QPromise<int> promise; + auto future = promise.future(); + + // Create a task that will await on the future + { + auto task = [](QFuture<int> future) -> QCoro::Task<> { + co_await future; + }(future); + + // Give the coroutine time to suspend by processing events + QTest::qWait(50); + + // Task is destroyed here when it goes out of scope + } + + // Now complete the future - this should not crash despite the awaiting task being destroyed + promise.start(); + promise.addResult(42); + promise.finish(); + + // Wait a bit to ensure any deferred cleanup happens + QTest::qWait(50); + + // If we got here without crashing, the test passed + } #endif private Q_SLOTS:
