Skip to content

Commit 88dfb74

Browse files
speak-agentclaude
andcommitted
the [c-abi.absent] parser is an internal helper, in the plainest form that compiles everywhere
clang 20.1.7 on Windows crashed generating code for this parser in three spellings: as a block inside `parse_string`, as a free function in the module's exported purview returning `std::expected<std::vector< CAbiAbsentEntry>, std::string>`, and with a pointer-to-member projection in its sort. Every other host and compiler took all three. What it takes is the plainest form available, and each part of that form is now measured rather than stylistic: an internal helper in the anonymous namespace rather than an inline function in the purview that every importer emits; an out parameter and `std::optional<std::string>` rather than `expected` over a vector of structs carrying strings; a comparator rather than a projection. The notes are beside it so a later tidy-up does not restore a shorter spelling and rediscover this on a Windows runner. Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 0f870c6 commit 88dfb74

1 file changed

Lines changed: 39 additions & 22 deletions

File tree

‎modules/manifest/src/toml.cppm‎

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -559,8 +559,6 @@ std::optional<std::string> find_disallowed_array_of_tables(
559559
return std::nullopt;
560560
}
561561

562-
} // namespace
563-
564562
// [c-abi.absent] — the facilities a C library does not supply (design
565563
// 2026-09-20 §5.7.5). The set of names it DOES supply is not enumerable in a
566564
// manifest; the exceptions are, and enumerating an exception is what lets a CI
@@ -577,36 +575,43 @@ std::optional<std::string> find_disallowed_array_of_tables(
577575
// the other two are departures from it, named so that a departure is something
578576
// that can be counted.
579577
//
580-
// A FREE FUNCTION AND NOT A BLOCK INSIDE `parse_string`, FOR A MEASURED
581-
// REASON. Written inline it crashed clang 20.1.7 on Windows during LLVM IR
582-
// generation of `parse_string` (exception 0xC0000005, the frame naming this
583-
// block's compound statement); every other host compiled it. This codebase has
584-
// met the shape before — a construct that is fine in a function and not in a
585-
// large one inside a module interface unit — and the answer is the same: give
586-
// it its own function.
587-
inline std::expected<std::vector<mcpp::targetside::CAbiAbsentEntry>, std::string>
588-
parse_c_abi_absent(const t::Value& v) {
589-
std::vector<mcpp::targetside::CAbiAbsentEntry> out;
578+
// THE SHAPE OF THIS FUNCTION IS MEASURED RATHER THAN STYLISTIC. Written as a
579+
// block inside `parse_string` it crashed clang 20.1.7 on Windows during LLVM
580+
// IR generation (exception 0xC0000005); moved out to a free function returning
581+
// `std::expected<std::vector<CAbiAbsentEntry>, std::string>` the crash moved
582+
// with it, now naming this function. Every other host and every other compiler
583+
// compiled all three spellings. What it takes is the plainest form available:
584+
// an out parameter and an optional error, with no `expected` over a vector of
585+
// structs carrying strings. The note is here so that a later tidy-up does not
586+
// restore a shorter spelling and rediscover this on a Windows runner.
587+
//
588+
// It also belongs in this anonymous namespace and not in the module's exported
589+
// purview, where the first two spellings were written. An inline function in
590+
// the purview is emitted into every importer of the module; this one is an
591+
// implementation detail of `parse_string` and has no reader outside it.
592+
inline std::optional<std::string>
593+
parse_c_abi_absent(const t::Value& v,
594+
std::vector<mcpp::targetside::CAbiAbsentEntry>& out) {
590595
if (!v.is_table())
591-
return std::unexpected(std::string(
596+
return std::optional<std::string>(std::string(
592597
"[c-abi.absent] must be a table of facility names, each with a "
593598
"`form`: fork = { form = \"link\" }"));
594599
for (auto const& kv : v.as_table()) {
595600
const std::string& name = kv.first;
596601
const t::Value& ent = kv.second;
597602
if (!ent.is_table())
598-
return std::unexpected(std::format(
603+
return std::optional<std::string>(std::format(
599604
"[c-abi.absent].{} must be a table with a `form`: "
600605
"{} = {{ form = \"link\" }}", name, name));
601606
const auto& et = ent.as_table();
602607
for (auto const& m : et)
603608
if (m.first != "form" && m.first != "note")
604-
return std::unexpected(std::format(
609+
return std::optional<std::string>(std::format(
605610
"[c-abi.absent].{} has no member '{}'; the members are: "
606611
"form, note", name, m.first));
607612
auto fit = et.find("form");
608613
if (fit == et.end() || !fit->second.is_string())
609-
return std::unexpected(std::format(
614+
return std::optional<std::string>(std::format(
610615
"[c-abi.absent].{} is missing `form`. An absence with no "
611616
"named shape is one nothing can assert against; the shapes "
612617
"are \"link\" (the definition is absent), \"enosys\" (it "
@@ -615,7 +620,7 @@ parse_c_abi_absent(const t::Value& v) {
615620
"it asked for is not done).", name));
616621
auto form = mcpp::targetside::parse_c_abi_absent_form(fit->second.as_string());
617622
if (!form)
618-
return std::unexpected(std::format(
623+
return std::optional<std::string>(std::format(
619624
"[c-abi.absent].{}.form = \"{}\" names no known shape. The "
620625
"shapes are \"link\", \"enosys\" and \"accepted-no-effect\".",
621626
name, fit->second.as_string()));
@@ -626,10 +631,23 @@ parse_c_abi_absent(const t::Value& v) {
626631
e.note = nit->second.as_string();
627632
out.push_back(std::move(e));
628633
}
629-
std::ranges::sort(out, {}, &mcpp::targetside::CAbiAbsentEntry::name);
630-
return out;
634+
// A PLAIN COMPARATOR AND NOT A PROJECTION. `std::ranges::sort(out, {},
635+
// &CAbiAbsentEntry::name)` says the same thing and crashed clang 20.1.7 on
636+
// Windows while generating code for this function (0xC0000005). A
637+
// pointer-to-member used as a projection is a shape this codebase has met
638+
// before on that front end; the note is here so the shorter spelling is
639+
// not restored as a tidy-up.
640+
std::sort(out.begin(), out.end(),
641+
[](const mcpp::targetside::CAbiAbsentEntry& a,
642+
const mcpp::targetside::CAbiAbsentEntry& b) {
643+
return a.name < b.name;
644+
});
645+
return std::nullopt;
631646
}
632647

648+
} // namespace
649+
650+
633651
std::expected<Manifest, ManifestError> parse_string(std::string_view content,
634652
const std::filesystem::path& origin,
635653
LoadContext ctx) {
@@ -1155,9 +1173,8 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
11551173
"builtins, data-model, presents, wchar", key)));
11561174
}
11571175
if (auto ait = ct->find("absent"); ait != ct->end()) {
1158-
auto absent = parse_c_abi_absent(ait->second);
1159-
if (!absent) return std::unexpected(error(origin, absent.error()));
1160-
decl.absent = std::move(*absent);
1176+
if (auto why = parse_c_abi_absent(ait->second, decl.absent))
1177+
return std::unexpected(error(origin, *why));
11611178
}
11621179
if (auto pit = ct->find("presents"); pit != ct->end()) {
11631180
if (!pit->second.is_string())

0 commit comments

Comments
 (0)