Skip to content

-Wno-shadow-uncaptured-local suppresses a different set on each clang, so the WASM leg is the only one enforcing structured-binding shadowing #662

Description

@Yaraslaut

What happens

cmake/compiler_options.cmake's Clang branch passes -Wno-shadow-uncaptured-local to every Clang-family target. Which diagnostics that flag actually suppresses turns out to differ between the clang releases morph builds with, and one diagnostic — declaration shadows a structured binding — falls inside the suppression on Linux clang 22.1.8 and outside it on the emsdk clang the WASM legs use.

The consequence is not cosmetic: that diagnostic class is enforced on exactly one CI leg, and no local build on the toolchain morph otherwise develops with can reproduce it. A developer can run a full -Weverything -Werror clang build, see it green, and still be rejected by the WASM leg for a warning that build was structurally unable to emit.

Verification status

Reproduced, on 7ab4c7a9 plus PR #661's first two commits (dbe10202), by both toolchains.

The WASM leg's own failure, from the log of run 35573507189 (job 106250062389):

/home/runner/work/morph/morph/include/morph/core/bridge.hpp:2112:38: error: declaration shadows a structured binding [-Werror,-Wshadow]
/home/runner/work/morph/morph/include/morph/core/bridge.hpp:2118:50: error: declaration shadows a structured binding [-Werror,-Wshadow]
/home/runner/work/morph/morph/include/morph/core/bridge.hpp:2297:50: error: declaration shadows a structured binding [-Werror,-Wshadow]
/home/runner/work/morph/morph/include/morph/core/bridge.hpp:2303:50: error: declaration shadows a structured binding [-Werror,-Wshadow]

The compile line in that same log contains -Wno-shadow-uncaptured-local, so the suppression was in effect and did not cover the diagnostic. Toolchain: emsdk 3.1.56, Qt 6.8.3 wasm_singlethread.

The same source, same project flags, on local clang 22.1.8:

$ clang++ -std=c++23 -fsyntax-only -Weverything sb.cpp
sb.cpp:12:15: warning: declaration shadows a structured binding [-Wshadow-uncaptured-local]

Note the group: -Wshadow-uncaptured-local, not -Wshadow. Because morph suppresses that group, a clang-22 build of the identical code is silent. Confirmed against the real tree, not just the reduction — a -Weverything -Werror clang 22.1.8 build of morph_tests at dbe10202 compiled all 58 affected TUs with zero diagnostics while the WASM leg was failing on four.

Minimal reproduction:

#include <functional>
#include <utility>
std::pair<std::function<void(int)>, std::function<void(int)>> make();
void sink(std::function<void()>);
void f() {
    auto [onRegistered, onFailed] = make();
    int newId = 1;
    auto outer = [onRegistered, newId]() mutable {
        sink([onRegistered = std::move(onRegistered), newId] { onRegistered(newId); });
    };
    outer();
}

Dropping just -Wno-shadow-uncaptured-local from the project's own flag list for a TU that includes bridge.hpp makes clang 22 report the same four sites the WASM leg reported, which is how PR #661 verified its fix locally:

before: shadows a local variable: 18   shadows a structured binding: 4
after:  shadows a local variable: 18

Not verified: which clang release the reclassification landed in, and whether any other diagnostic is split across the two groups the same way. Only shadows a structured binding and shadows a local variable were observed, and only the first diverges. No emsdk toolchain was available locally, so every emsdk-side statement here is read off the CI log rather than run.

Why this is worth fixing rather than living with

The suppression's intent — spelled out in compiler_options.cmake as "lambda param shadowing an uncaptured local" — is about local variables. Shadowing a structured binding from an init-capture that moves from it is a different and arguably more interesting construct, and nothing recorded suggests anyone decided to suppress it. It is suppressed on most legs by accident of how one clang release groups diagnostics.

The reverse case is the worry: a diagnostic that today only the emsdk clang reports could tomorrow be reclassified the other way, and a class of warning would go from "enforced on one leg" to "enforced nowhere" with no CI change and nothing to notice it.

Possible remedies (not evaluated)

  • Narrow the suppression to what it says it is, e.g. re-enable -Wshadow explicitly after -Wno-shadow-uncaptured-local, and fix or individually suppress the 18 pre-existing local-variable shadows in backend.hpp, completion.hpp, registry.hpp, callback_scope.hpp, bridge.hpp.
  • Keep the suppression but add a Linux-clang leg that drops it, so the WASM leg stops being the sole enforcer.
  • Decide the construct is fine and suppress it by name on both, which at least records the decision.

What would change the verdict

  • Close as invalid if the divergence is an artifact of the reduction rather than the group assignment — i.e. if an emsdk build with -Wno-shadow-uncaptured-local does suppress shadows a structured binding and the four CI errors had some other cause.
  • Close as fixed once a diagnostic in this class fails a leg that a developer can run locally on the toolchain morph is built with, demonstrated by mutating the code and watching that leg go red.

Found while fixing the four -Wshadow errors in PR #661; filed rather than folded in, per AGENTS.md.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ciSubsystem: cibugSomething isn't workingtriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions