// mcpp.modgraph.graph DAG of module units (per-package, cross-package later).
export module mcpp.modgraph.graph;
import std;
import mcpp.source_kind;
export namespace mcpp::modgraph {
struct ModuleId {
std::string logicalName; // "mcpplibs.hello.core"
auto operator(const ModuleId&) const = default;
};
struct SourceUnit {
std::filesystem::path path;
// mcpp#233: path relative to this unit's PACKAGE ROOT (not the primary
// project root a path dependency has its own root), set by the
// scanner where the root is known. Used by plan.cppm to mirror the
// source's directory layout into its object path so that two
// same-named files under different subdirectories (e.g. a/src/util.cpp
// and b/src/util.cpp) never fold onto the same object output. Left
// default-constructed (empty) for units synthesized outside the
// scanner (e.g. plan.cppm's ad-hoc main.cpp CompileUnit, which never
// reads this field).
std::filesystem::path relPath;
std::string packageName;
std::vector localIncludeDirs;
// #249: dirs emitted as -idirafter searched after the toolchain's
// system dirs, so a dep's source root can't shadow standard headers.
std::vector localIncludeDirsAfter;
std::vector packageCflags;
std::vector packageCxxflags;
std::vector packageAsmflags; // per-glob asmflags (G4)
std::optional provides;
// Was `provides` declared with `export module`?
//
// Both spellings produce a BMI and an object, so `provides` alone cannot
// tell an INTERFACE partition (`export module M:api;`) from an
// IMPLEMENTATION partition (`module M:impl;`) and the difference decides
// whether a source may be published. `mcpp pack` publishes the module
// closure of the lib root; a closure that reaches an implementation
// partition has to publish it too (the consumer cannot build the BMI
// without it), and the author needs to be told that their implementation
// is going out.
//
// Three states, because "nobody determined this" is a real answer and it
// used to be spelled `true`:
//
// true `export module M:api;` the text scanner read the keyword
// false `module M:impl;` likewise
// nullopt nobody could tell: a `scan_overrides` entry names the module
// but has no way to say whether it is exported, and a P1689
// scanner may omit `is-interface`
//
// It defaulted to `true` and was called the conservative direction "since
// the flag only ever produces a warning". That had it backwards: `true` is
// the value that produces NO warning, so an undetermined implementation
// partition was published in silence the exact failure this field exists
// to prevent. Unknown now warns, naming the file and the reason.
std::optional providesInterface;
std::vector requires_;
// The unit's ROLE, decided once by the scanner from the owning package's
// extension table and carried from here on. Every downstream consumer
// (planner, backend, compile_commands, the asm dialect check) reads this
// instead of re-deriving it from the extension see mcpp.source_kind for
// why that mattered.
mcpp::SourceKind kind = mcpp::SourceKind::Other;
//
// `isModuleInterface` / `isImplementation` used to live here. They were
// WRITE-ONLY: three sites derived them (the scanner said `.cpp`, the p1689
// reader said `.cpp || .cxx`, the scan_overrides branch said "has
// provides"), the three disagreed, and nothing in src/ or tests/ ever read
// the result. Converging three derivations of a value nobody reads is
// still three derivations, so they are gone instead. `provides` answers
// "is this an interface"; `kind` answers the rest.
// Unit built from a manifest scan_overrides declaration instead of a
// real scan plan-vs-ddi verification is mandatory for these.
bool scanOverridden = false;
};
struct Graph {
std::vector units;
// logical-name -> index into units
std::map producerOf;
// edges as (consumer-index, producer-index)
std::vector edges;
};
// Topological order: returns indices of units in producer-before-consumer order.
// Returns std::unexpected with the cycle if any.
struct CycleError {
std::vector cycle;
};
std::expected topo_sort(const Graph& g);
} // namespace mcpp::modgraph
namespace mcpp::modgraph {
std::expected topo_sort(const Graph& g) {
std::vector indeg(g.units.size(), 0);
std::vector adj(g.units.size());
for (auto [c, p] : g.edges) {
// edge means: consumer depends on producer. So producer must come first.
// indegree of consumer counts unmet producer dependencies.
indeg[c]++;
adj[p].push_back(c);
}
std::vector order;
order.reserve(g.units.size());
std::vector queue;
for (std::size_t i = 0; i < indeg.size(); ++i) {
if (indeg[i] == 0) queue.push_back(i);
}
while (!queue.empty()) {
std::size_t u = queue.back();
queue.pop_back();
order.push_back(u);
for (auto v : adj[u]) {
if (--indeg[v] == 0) queue.push_back(v);
}
}
if (order.size() != g.units.size()) {
// Cycle remains. Report units still with positive indegree.
CycleError err;
for (std::size_t i = 0; i < indeg.size(); ++i) {
if (indeg[i] > 0) err.cycle.push_back(i);
}
return std::unexpected(err);
}
return order;
}
} // namespace mcpp::modgraph