use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::path::PathBuf;
use super::cpp_modules::{ModuleDecl, ModuleImport, ModuleMap, ModuleName};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleUnitKind {
ModuleInterface,
ModuleImplementation,
ModulePartitionInterface,
ModulePartitionImplementation,
GlobalModuleFragment,
PrivateModuleFragment,
HeaderUnit,
}
impl ModuleUnitKind {
pub fn is_interface(&self) -> bool {
matches!(
self,
ModuleUnitKind::ModuleInterface
| ModuleUnitKind::ModulePartitionInterface
| ModuleUnitKind::HeaderUnit
)
}
pub fn is_partition(&self) -> bool {
matches!(
self,
ModuleUnitKind::ModulePartitionInterface
| ModuleUnitKind::ModulePartitionImplementation
)
}
pub fn label(&self) -> &'static str {
match self {
ModuleUnitKind::ModuleInterface => "module interface",
ModuleUnitKind::ModuleImplementation => "module implementation",
ModuleUnitKind::ModulePartitionInterface => "module partition interface",
ModuleUnitKind::ModulePartitionImplementation => "module partition implementation",
ModuleUnitKind::GlobalModuleFragment => "global module fragment",
ModuleUnitKind::PrivateModuleFragment => "private module fragment",
ModuleUnitKind::HeaderUnit => "header unit",
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleUnit {
pub name: ModuleName,
pub kind: ModuleUnitKind,
pub partition: Option<String>,
pub source_path: PathBuf,
pub bmi_path: Option<PathBuf>,
pub is_compiled: bool,
pub is_primary: bool,
pub imports: Vec<ModuleName>,
pub partition_imports: Vec<(ModuleName, String)>,
pub header_imports: Vec<String>,
pub exported_decls: Vec<String>,
pub owned_decls: Vec<String>,
pub source_content: String,
}
impl ModuleUnit {
pub fn new(name: ModuleName, kind: ModuleUnitKind, source: &str) -> Self {
Self {
name,
kind,
partition: None,
source_path: PathBuf::from(source),
bmi_path: None,
is_compiled: false,
is_primary: kind == ModuleUnitKind::ModuleInterface,
imports: Vec::new(),
partition_imports: Vec::new(),
header_imports: Vec::new(),
exported_decls: Vec::new(),
owned_decls: Vec::new(),
source_content: String::new(),
}
}
pub fn with_partition(mut self, partition: &str) -> Self {
self.partition = Some(partition.to_string());
self
}
pub fn add_import(&mut self, module: ModuleName) {
if !self.imports.contains(&module) {
self.imports.push(module);
}
}
pub fn add_partition_import(&mut self, module: ModuleName, partition: &str) {
self.partition_imports.push((module, partition.to_string()));
}
pub fn add_header_import(&mut self, header: &str) {
self.header_imports.push(header.to_string());
}
pub fn add_exported_decl(&mut self, decl: &str) {
self.exported_decls.push(decl.to_string());
}
pub fn add_owned_decl(&mut self, decl: &str) {
self.owned_decls.push(decl.to_string());
}
pub fn set_bmi_path(&mut self, path: &str) {
self.bmi_path = Some(PathBuf::from(path));
}
pub fn mark_compiled(&mut self) {
self.is_compiled = true;
}
pub fn all_decls(&self) -> Vec<&str> {
self.exported_decls
.iter()
.map(|s| s.as_str())
.chain(self.owned_decls.iter().map(|s| s.as_str()))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct ModuleDatabase {
pub units: HashMap<String, Vec<ModuleUnit>>,
pub primary_interfaces: HashMap<String, usize>,
pub bmi_cache_dir: Option<PathBuf>,
pub search_paths: Vec<PathBuf>,
pub dependency_graph: HashMap<String, HashSet<String>>,
pub reachability: HashMap<String, HashSet<String>>,
}
impl ModuleDatabase {
pub fn new() -> Self {
Self {
units: HashMap::new(),
primary_interfaces: HashMap::new(),
bmi_cache_dir: None,
search_paths: Vec::new(),
dependency_graph: HashMap::new(),
reachability: HashMap::new(),
}
}
pub fn register_unit(&mut self, unit: ModuleUnit) {
let name = unit.name.to_string();
let is_primary = unit.is_primary;
let entry = self.units.entry(name.clone()).or_default();
let idx = entry.len();
entry.push(unit);
if is_primary {
self.primary_interfaces.insert(name.clone(), idx);
}
}
pub fn primary_interface(&self, module_name: &str) -> Option<&ModuleUnit> {
self.primary_interfaces
.get(module_name)
.and_then(|&idx| self.units.get(module_name).and_then(|units| units.get(idx)))
}
pub fn find_partition(&self, module_name: &str, partition: &str) -> Option<&ModuleUnit> {
self.units.get(module_name).and_then(|units| {
units
.iter()
.find(|u| u.partition.as_deref() == Some(partition))
})
}
pub fn resolve_import(&self, module_name: &str) -> Option<ModuleResolution> {
if let Some(primary) = self.primary_interface(module_name) {
if let Some(ref bmi) = primary.bmi_path {
return Some(ModuleResolution::Bmi(bmi.clone()));
}
return Some(ModuleResolution::Source(primary.source_path.clone()));
}
if let Some(ref cache_dir) = self.bmi_cache_dir {
let bmi_path = cache_dir.join(format!("{}.bmi", module_name));
if bmi_path.exists() {
return Some(ModuleResolution::Bmi(bmi_path));
}
}
for path in &self.search_paths {
let source_path = path.join(format!("{}.cppm", module_name));
if source_path.exists() {
return Some(ModuleResolution::Source(source_path));
}
let alt_path = path.join(format!("{}.ixx", module_name));
if alt_path.exists() {
return Some(ModuleResolution::Source(alt_path));
}
}
None
}
pub fn build_dependency_graph(&mut self) {
self.dependency_graph.clear();
for (module_name, units) in &self.units {
let mut deps = HashSet::new();
for unit in units {
for imp in &unit.imports {
deps.insert(imp.to_string());
}
for (imp, _part) in &unit.partition_imports {
deps.insert(imp.to_string());
}
}
self.dependency_graph.insert(module_name.clone(), deps);
}
}
pub fn compute_reachability(&mut self) {
self.reachability.clear();
for module_name in self.units.keys() {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(module_name.clone());
while let Some(current) = queue.pop_front() {
if !visited.insert(current.clone()) {
continue;
}
if let Some(deps) = self.dependency_graph.get(¤t) {
for dep in deps {
if !visited.contains(dep) {
queue.push_back(dep.clone());
}
}
}
}
visited.remove(module_name); self.reachability.insert(module_name.clone(), visited);
}
}
pub fn is_reachable(&self, from: &str, to: &str) -> bool {
if from == to {
return true;
}
self.reachability
.get(from)
.map_or(false, |r| r.contains(to))
}
pub fn topological_order(&self) -> Vec<String> {
let mut in_degree: HashMap<String, usize> = HashMap::new();
for name in self.units.keys() {
in_degree.entry(name.clone()).or_insert(0);
}
for (name, deps) in &self.dependency_graph {
for dep in deps {
if self.units.contains_key(dep) {
*in_degree.entry(dep.clone()).or_insert(0) += 1;
}
}
}
let mut queue: VecDeque<String> = in_degree
.iter()
.filter(|(_, °)| deg == 0)
.map(|(n, _)| n.clone())
.collect();
let mut order = Vec::new();
while let Some(current) = queue.pop_front() {
order.push(current.clone());
if let Some(deps) = self.dependency_graph.get(¤t) {
for dep in deps {
if let Some(deg) = in_degree.get_mut(dep) {
*deg = deg.saturating_sub(1);
if *deg == 0 {
queue.push_back(dep.clone());
}
}
}
}
}
order
}
pub fn has_cycles(&self) -> bool {
let order = self.topological_order();
order.len() != self.units.len()
}
pub fn module_names(&self) -> Vec<&String> {
self.units.keys().collect()
}
pub fn len(&self) -> usize {
self.units.len()
}
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
}
impl Default for ModuleDatabase {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum ModuleResolution {
Bmi(PathBuf),
Source(PathBuf),
HeaderUnit(String),
}
#[derive(Debug, Clone)]
pub struct BmiHeader {
pub magic: [u8; 4],
pub version: u32,
pub module_name: ModuleName,
pub is_interface: bool,
pub compiler_version: String,
pub target_triple: String,
pub import_count: u32,
pub exported_count: u32,
pub owned_count: u32,
pub source_checksum: u64,
pub data_size: u64,
}
impl Default for BmiHeader {
fn default() -> Self {
Self {
magic: *b"BMI\0",
version: 1,
module_name: ModuleName::new(Vec::new()),
is_interface: false,
compiler_version: "llvm-native 1.0".to_string(),
target_triple: "x86_64-unknown-linux-gnu".to_string(),
import_count: 0,
exported_count: 0,
owned_count: 0,
source_checksum: 0,
data_size: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct BinaryModuleInterface {
pub header: BmiHeader,
pub imports: Vec<ModuleImport>,
pub exported_ast: Vec<u8>,
pub owned_ast: Vec<u8>,
pub raw_data: Vec<u8>,
}
impl BinaryModuleInterface {
pub fn new(module_name: ModuleName) -> Self {
let mut header = BmiHeader::default();
header.module_name = module_name;
Self {
header,
imports: Vec::new(),
exported_ast: Vec::new(),
owned_ast: Vec::new(),
raw_data: Vec::new(),
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&self.header.magic);
data.extend_from_slice(&self.header.version.to_le_bytes());
let name_str = self.header.module_name.to_string();
let name_bytes = name_str.as_bytes();
data.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
data.extend_from_slice(name_bytes);
data.push(if self.header.is_interface { 1 } else { 0 });
let cv = self.header.compiler_version.as_bytes();
data.extend_from_slice(&(cv.len() as u32).to_le_bytes());
data.extend_from_slice(cv);
let tt = self.header.target_triple.as_bytes();
data.extend_from_slice(&(tt.len() as u32).to_le_bytes());
data.extend_from_slice(tt);
data.extend_from_slice(&(self.imports.len() as u32).to_le_bytes());
for imp in &self.imports {
let imp_str = imp.module.to_string();
let ib = imp_str.as_bytes();
data.extend_from_slice(&(ib.len() as u32).to_le_bytes());
data.extend_from_slice(ib);
}
data.extend_from_slice(&(self.exported_ast.len() as u64).to_le_bytes());
data.extend_from_slice(&self.exported_ast);
data.extend_from_slice(&(self.owned_ast.len() as u64).to_le_bytes());
data.extend_from_slice(&self.owned_ast);
data.extend_from_slice(&self.header.source_checksum.to_le_bytes());
data
}
pub fn deserialize(data: &[u8]) -> Option<Self> {
if data.len() < 20 {
return None;
}
let mut pos = 0;
let magic: [u8; 4] = data[pos..pos + 4].try_into().ok()?;
if &magic != b"BMI\0" {
return None;
}
pos += 4;
let version = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let name_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
let name_str = std::str::from_utf8(&data[pos..pos + name_len]).ok()?;
pos += name_len;
let module_name = ModuleName::from_str(name_str);
let is_interface = data[pos] != 0;
pos += 1;
let cv_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
let compiler_version = std::str::from_utf8(&data[pos..pos + cv_len])
.ok()?
.to_string();
pos += cv_len;
let tt_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
let target_triple = std::str::from_utf8(&data[pos..pos + tt_len])
.ok()?
.to_string();
pos += tt_len;
let import_count = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
let mut imports = Vec::new();
for _ in 0..import_count {
let ilen = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
let imp_str = std::str::from_utf8(&data[pos..pos + ilen]).ok()?;
pos += ilen;
imports.push(ModuleImport::new(ModuleName::from_str(imp_str)));
}
let exported_len = u64::from_le_bytes(data[pos..pos + 8].try_into().ok()?) as usize;
pos += 8;
let exported_ast = data[pos..pos + exported_len].to_vec();
pos += exported_len;
let owned_len = u64::from_le_bytes(data[pos..pos + 8].try_into().ok()?) as usize;
pos += 8;
let owned_ast = data[pos..pos + owned_len].to_vec();
pos += owned_len;
let source_checksum = u64::from_le_bytes(data[pos..pos + 8].try_into().ok()?);
Some(Self {
header: BmiHeader {
magic,
version,
module_name,
is_interface,
compiler_version,
target_triple,
import_count: import_count as u32,
exported_count: exported_ast.len() as u32,
owned_count: owned_ast.len() as u32,
source_checksum,
data_size: data.len() as u64,
},
imports,
exported_ast,
owned_ast,
raw_data: data.to_vec(),
})
}
pub fn is_compatible(&self, expected_version: &str) -> bool {
self.header.compiler_version == expected_version
}
pub fn compute_source_checksum(source: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in source.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
}
#[derive(Debug, Clone)]
pub struct ModuleOwnershipTracker {
pub ownership: HashMap<String, ModuleName>,
pub definitions: HashMap<String, ModuleName>,
pub odr_violations: Vec<OdrViolation>,
}
impl ModuleOwnershipTracker {
pub fn new() -> Self {
Self {
ownership: HashMap::new(),
definitions: HashMap::new(),
odr_violations: Vec::new(),
}
}
pub fn register_declaration(&mut self, decl_name: &str, module: ModuleName) {
self.ownership.insert(decl_name.to_string(), module);
}
pub fn register_definition(&mut self, decl_name: &str, module: ModuleName) {
if let Some(existing) = self.definitions.get(decl_name) {
if existing != &module {
self.odr_violations.push(OdrViolation {
declaration: decl_name.to_string(),
module_a: existing.clone(),
module_b: module,
violation_kind: OdrViolationKind::MultipleDefinitions,
});
}
} else {
self.definitions.insert(decl_name.to_string(), module);
}
}
pub fn is_exported_by(&self, decl_name: &str, module: &ModuleName) -> bool {
self.ownership
.get(decl_name)
.map_or(false, |owner| owner == module)
}
pub fn owner_of(&self, decl_name: &str) -> Option<&ModuleName> {
self.ownership.get(decl_name)
}
pub fn clear(&mut self) {
self.ownership.clear();
self.definitions.clear();
self.odr_violations.clear();
}
}
impl Default for ModuleOwnershipTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OdrViolation {
pub declaration: String,
pub module_a: ModuleName,
pub module_b: ModuleName,
pub violation_kind: OdrViolationKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OdrViolationKind {
MultipleDefinitions,
InconsistentDefinitions,
MissingDefinition,
UnreachableDefinition,
}
#[derive(Debug, Clone)]
pub struct HeaderUnit {
pub header_name: String,
pub is_system_header: bool,
pub bmi_synthesized: bool,
pub bmi_path: Option<PathBuf>,
pub header_path: PathBuf,
pub synthetic_module_name: ModuleName,
pub declarations: Vec<String>,
}
impl HeaderUnit {
pub fn new(header_name: &str, header_path: &str, is_system: bool) -> Self {
let clean_name = header_name
.trim_matches(|c: char| c == '<' || c == '>' || c == '"' || c == '.')
.replace('.', "_")
.replace('/', "_");
let synthetic_name = format!("__header_{}", clean_name);
Self {
header_name: header_name.to_string(),
is_system_header: is_system,
bmi_synthesized: false,
bmi_path: None,
header_path: PathBuf::from(header_path),
synthetic_module_name: ModuleName::from_str(&synthetic_name),
declarations: Vec::new(),
}
}
pub fn synthesize_bmi(&mut self, bmi_path: &str) {
self.bmi_synthesized = true;
self.bmi_path = Some(PathBuf::from(bmi_path));
}
pub fn add_declaration(&mut self, decl: &str) {
self.declarations.push(decl.to_string());
}
}
#[derive(Debug, Clone)]
pub struct ModuleMapEntry {
pub name: ModuleName,
pub is_framework: bool,
pub is_explicit: bool,
pub headers: Vec<ModuleMapHeader>,
pub submodules: Vec<ModuleMapEntry>,
pub exports: Vec<ModuleName>,
pub uses: Vec<ModuleName>,
pub link_libraries: Vec<String>,
}
impl ModuleMapEntry {
pub fn new(name: ModuleName) -> Self {
Self {
name,
is_framework: false,
is_explicit: false,
headers: Vec::new(),
submodules: Vec::new(),
exports: Vec::new(),
uses: Vec::new(),
link_libraries: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleMapHeader {
pub path: PathBuf,
pub is_textual: bool,
pub is_private: bool,
pub is_umbrella: bool,
pub size: u64,
}
impl ModuleMapHeader {
pub fn new(path: &str) -> Self {
Self {
path: PathBuf::from(path),
is_textual: false,
is_private: false,
is_umbrella: false,
size: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleMapFile {
pub path: PathBuf,
pub modules: Vec<ModuleMapEntry>,
pub is_parsed: bool,
}
impl ModuleMapFile {
pub fn new(path: &str) -> Self {
Self {
path: PathBuf::from(path),
modules: Vec::new(),
is_parsed: false,
}
}
pub fn parse(&mut self, content: &str) {
self.modules.clear();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") {
continue;
}
let tokens: Vec<&str> = trimmed.split_whitespace().collect();
if tokens.is_empty() {
continue;
}
match tokens[0] {
"module" if tokens.len() >= 2 => {
let name = tokens[1].trim_end_matches('{');
let mut entry = ModuleMapEntry::new(ModuleName::from_str(name));
if trimmed.contains("framework") {
entry.is_framework = true;
}
if trimmed.contains("explicit") {
entry.is_explicit = true;
}
self.modules.push(entry);
}
"header" if tokens.len() >= 2 => {
let path = tokens[1].trim_matches('"');
if let Some(last) = self.modules.last_mut() {
last.headers.push(ModuleMapHeader::new(path));
}
}
"export" if tokens.len() >= 2 => {
if let Some(last) = self.modules.last_mut() {
last.exports
.push(ModuleName::from_str(tokens[1].trim_end_matches(',')));
}
}
"link" if tokens.len() >= 2 => {
if let Some(last) = self.modules.last_mut() {
last.link_libraries
.push(tokens[1].trim_matches('"').to_string());
}
}
"umbrella" if tokens.len() >= 2 => {
let path = tokens[1].trim_matches('"');
if let Some(last) = self.modules.last_mut() {
let mut h = ModuleMapHeader::new(path);
h.is_umbrella = true;
last.headers.push(h);
}
}
_ => {}
}
}
self.is_parsed = true;
}
pub fn module_names(&self) -> Vec<String> {
self.modules.iter().map(|m| m.name.to_string()).collect()
}
pub fn find_module(&self, name: &str) -> Option<&ModuleMapEntry> {
self.modules.iter().find(|m| m.name.to_string() == name)
}
}
pub struct ModuleCompilationOrchestrator {
pub database: ModuleDatabase,
pub ownership: ModuleOwnershipTracker,
pub header_units: HashMap<String, HeaderUnit>,
pub module_maps: Vec<ModuleMapFile>,
pub rebuild_stale: bool,
pub verify_compatibility: bool,
}
impl ModuleCompilationOrchestrator {
pub fn new() -> Self {
Self {
database: ModuleDatabase::new(),
ownership: ModuleOwnershipTracker::new(),
header_units: HashMap::new(),
module_maps: Vec::new(),
rebuild_stale: true,
verify_compatibility: true,
}
}
pub fn add_unit(&mut self, unit: ModuleUnit) {
self.database.register_unit(unit);
}
pub fn add_header_unit(&mut self, header: HeaderUnit) {
self.header_units.insert(header.header_name.clone(), header);
}
pub fn load_module_map(&mut self, path: &str, content: &str) {
let mut map = ModuleMapFile::new(path);
map.parse(content);
self.module_maps.push(map);
}
pub fn compilation_order(&self) -> Vec<String> {
self.database.topological_order()
}
pub fn check_odr(&mut self) -> Vec<OdrViolation> {
self.ownership.odr_violations.clone()
}
pub fn generate_dependency_report(&self) -> String {
let mut report = String::new();
report.push_str("# Module Dependency Report\n\n");
for (name, units) in &self.database.units {
report.push_str(&format!("## Module: {}\n\n", name));
for unit in units {
report.push_str(&format!(
"- Unit: {} ({})\n",
unit.source_path.display(),
unit.kind.label()
));
if let Some(ref part) = unit.partition {
report.push_str(&format!(" Partition: {}\n", part));
}
if !unit.imports.is_empty() {
report.push_str(" Imports:\n");
for imp in &unit.imports {
report.push_str(&format!(" - {}\n", imp));
}
}
if !unit.header_imports.is_empty() {
report.push_str(" Header imports:\n");
for h in &unit.header_imports {
report.push_str(&format!(" - {}\n", h));
}
}
}
report.push('\n');
}
if let Some(reach) = self.database.reachability.get("") {
report.push_str("Reachability computed.\n");
}
report
}
pub fn summary(&self) -> String {
format!(
"Modules: {} total, {} headers, {} module maps loaded, topological order: {:?}",
self.database.len(),
self.header_units.len(),
self.module_maps.len(),
self.compilation_order(),
)
}
}
impl Default for ModuleCompilationOrchestrator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_unit_kind_labels() {
assert_eq!(ModuleUnitKind::ModuleInterface.label(), "module interface");
assert_eq!(
ModuleUnitKind::ModuleImplementation.label(),
"module implementation"
);
assert!(ModuleUnitKind::ModuleInterface.is_interface());
assert!(!ModuleUnitKind::ModuleImplementation.is_interface());
}
#[test]
fn test_module_unit_kind_is_partition() {
assert!(ModuleUnitKind::ModulePartitionInterface.is_partition());
assert!(!ModuleUnitKind::ModuleInterface.is_partition());
}
#[test]
fn test_module_unit_new() {
let unit = ModuleUnit::new(
ModuleName::from_str("std.core"),
ModuleUnitKind::ModuleInterface,
"src/std.core.cppm",
);
assert_eq!(unit.name.to_string(), "std.core");
assert!(unit.is_interface());
assert!(unit.is_primary);
}
#[test]
fn test_module_unit_with_partition() {
let unit = ModuleUnit::new(
ModuleName::from_str("std"),
ModuleUnitKind::ModulePartitionInterface,
"src/std-io.cppm",
)
.with_partition("io");
assert_eq!(unit.partition, Some("io".to_string()));
}
#[test]
fn test_module_unit_add_import() {
let mut unit = ModuleUnit::new(
ModuleName::from_str("mymod"),
ModuleUnitKind::ModuleInterface,
"src/mod.cppm",
);
unit.add_import(ModuleName::from_str("std.core"));
assert_eq!(unit.imports.len(), 1);
}
#[test]
fn test_module_unit_add_exported_decl() {
let mut unit = ModuleUnit::new(
ModuleName::from_str("mymod"),
ModuleUnitKind::ModuleInterface,
"src/mod.cppm",
);
unit.add_exported_decl("int f();");
unit.add_owned_decl("static int x;");
assert_eq!(unit.exported_decls.len(), 1);
assert_eq!(unit.owned_decls.len(), 1);
assert_eq!(unit.all_decls().len(), 2);
}
#[test]
fn test_database_register_unit() {
let mut db = ModuleDatabase::new();
let unit = ModuleUnit::new(
ModuleName::from_str("mymod"),
ModuleUnitKind::ModuleInterface,
"src/mymod.cppm",
);
db.register_unit(unit);
assert_eq!(db.len(), 1);
assert!(db.primary_interface("mymod").is_some());
}
#[test]
fn test_database_resolve_import() {
let mut db = ModuleDatabase::new();
let mut unit = ModuleUnit::new(
ModuleName::from_str("mymod"),
ModuleUnitKind::ModuleInterface,
"src/mymod.cppm",
);
unit.set_bmi_path("/cache/mymod.bmi");
db.register_unit(unit);
let resolution = db.resolve_import("mymod");
assert!(resolution.is_some());
}
#[test]
fn test_database_dependency_graph() {
let mut db = ModuleDatabase::new();
let mut mod_a = ModuleUnit::new(
ModuleName::from_str("A"),
ModuleUnitKind::ModuleInterface,
"A.cppm",
);
mod_a.add_import(ModuleName::from_str("B"));
let mod_b = ModuleUnit::new(
ModuleName::from_str("B"),
ModuleUnitKind::ModuleInterface,
"B.cppm",
);
db.register_unit(mod_a);
db.register_unit(mod_b);
db.build_dependency_graph();
assert!(db.dependency_graph.contains_key("A"));
assert!(db.dependency_graph["A"].contains("B"));
}
#[test]
fn test_database_topological_order() {
let mut db = ModuleDatabase::new();
let mut mod_a = ModuleUnit::new(
ModuleName::from_str("A"),
ModuleUnitKind::ModuleInterface,
"A.cppm",
);
mod_a.add_import(ModuleName::from_str("B"));
let mod_b = ModuleUnit::new(
ModuleName::from_str("B"),
ModuleUnitKind::ModuleInterface,
"B.cppm",
);
db.register_unit(mod_a);
db.register_unit(mod_b);
db.build_dependency_graph();
let order = db.topological_order();
assert_eq!(order.len(), 2);
assert!(order[0] == "B"); assert!(order[1] == "A");
}
#[test]
fn test_database_no_cycles() {
let mut db = ModuleDatabase::new();
let mod_a = ModuleUnit::new(
ModuleName::from_str("A"),
ModuleUnitKind::ModuleInterface,
"A.cppm",
);
let mod_b = ModuleUnit::new(
ModuleName::from_str("B"),
ModuleUnitKind::ModuleInterface,
"B.cppm",
);
db.register_unit(mod_a);
db.register_unit(mod_b);
db.build_dependency_graph();
assert!(!db.has_cycles());
}
#[test]
fn test_database_compute_reachability() {
let mut db = ModuleDatabase::new();
let mut mod_a = ModuleUnit::new(
ModuleName::from_str("A"),
ModuleUnitKind::ModuleInterface,
"A.cppm",
);
mod_a.add_import(ModuleName::from_str("B"));
let mut mod_b = ModuleUnit::new(
ModuleName::from_str("B"),
ModuleUnitKind::ModuleInterface,
"B.cppm",
);
mod_b.add_import(ModuleName::from_str("C"));
let mod_c = ModuleUnit::new(
ModuleName::from_str("C"),
ModuleUnitKind::ModuleInterface,
"C.cppm",
);
db.register_unit(mod_a);
db.register_unit(mod_b);
db.register_unit(mod_c);
db.build_dependency_graph();
db.compute_reachability();
assert!(db.is_reachable("A", "C"));
}
#[test]
fn test_bmi_serialize_deserialize() {
let name = ModuleName::from_str("test.mod");
let mut bmi = BinaryModuleInterface::new(name);
bmi.imports
.push(ModuleImport::new(ModuleName::from_str("std.core")));
bmi.exported_ast = b"int f(); int g();".to_vec();
bmi.header.source_checksum = BinaryModuleInterface::compute_source_checksum("test");
let serialized = bmi.serialize();
let deserialized = BinaryModuleInterface::deserialize(&serialized);
assert!(deserialized.is_some());
let deser = deserialized.unwrap();
assert_eq!(deser.header.module_name.to_string(), "test.mod");
assert_eq!(deser.imports.len(), 1);
assert_eq!(deser.exported_ast, b"int f(); int g();".to_vec());
}
#[test]
fn test_bmi_compatibility_check() {
let bmi = BinaryModuleInterface::new(ModuleName::from_str("test"));
assert!(bmi.is_compatible("llvm-native 1.0"));
assert!(!bmi.is_compatible("other-compiler 2.0"));
}
#[test]
fn test_bmi_source_checksum() {
let cs1 = BinaryModuleInterface::compute_source_checksum("hello");
let cs2 = BinaryModuleInterface::compute_source_checksum("hello");
assert_eq!(cs1, cs2);
let cs3 = BinaryModuleInterface::compute_source_checksum("world");
assert_ne!(cs1, cs3);
}
#[test]
fn test_ownership_tracker_register() {
let mut tracker = ModuleOwnershipTracker::new();
tracker.register_declaration("f", ModuleName::from_str("A"));
assert_eq!(tracker.owner_of("f").unwrap().to_string(), "A");
}
#[test]
fn test_ownership_tracker_is_exported_by() {
let mut tracker = ModuleOwnershipTracker::new();
tracker.register_declaration("f", ModuleName::from_str("A"));
assert!(tracker.is_exported_by("f", &ModuleName::from_str("A")));
assert!(!tracker.is_exported_by("f", &ModuleName::from_str("B")));
}
#[test]
fn test_ownership_tracker_odr_violation() {
let mut tracker = ModuleOwnershipTracker::new();
tracker.register_definition("f", ModuleName::from_str("A"));
tracker.register_definition("f", ModuleName::from_str("B"));
assert_eq!(tracker.odr_violations.len(), 1);
}
#[test]
fn test_header_unit_new() {
let header = HeaderUnit::new("<vector>", "/usr/include/c++/vector", true);
assert!(header.is_system_header);
assert_eq!(header.synthetic_module_name.to_string(), "__header_vector");
}
#[test]
fn test_header_unit_synthesize_bmi() {
let mut header = HeaderUnit::new("<iostream>", "/usr/include/c++/iostream", true);
header.synthesize_bmi("/cache/iostream.bmi");
assert!(header.bmi_synthesized);
assert!(header.bmi_path.is_some());
}
#[test]
fn test_module_map_parse() {
let content = r#"
module std {
header "string"
header "vector"
export *
}
"#;
let mut map = ModuleMapFile::new("module.modulemap");
map.parse(content);
assert!(map.is_parsed);
assert_eq!(map.modules.len(), 1);
assert_eq!(map.modules[0].headers.len(), 2);
}
#[test]
fn test_module_map_find() {
let content = "module Foo { header \"foo.h\" }";
let mut map = ModuleMapFile::new("module.modulemap");
map.parse(content);
assert!(map.find_module("Foo").is_some());
assert!(map.find_module("Bar").is_none());
}
#[test]
fn test_module_map_with_link() {
let content = "module Bar { header \"bar.h\" link \"z\" }";
let mut map = ModuleMapFile::new("module.modulemap");
map.parse(content);
assert_eq!(map.modules[0].link_libraries.len(), 1);
assert_eq!(map.modules[0].link_libraries[0], "z");
}
#[test]
fn test_orchestrator_add_unit() {
let mut orch = ModuleCompilationOrchestrator::new();
let unit = ModuleUnit::new(
ModuleName::from_str("test"),
ModuleUnitKind::ModuleInterface,
"test.cppm",
);
orch.add_unit(unit);
assert_eq!(orch.database.len(), 1);
}
#[test]
fn test_orchestrator_add_header_unit() {
let mut orch = ModuleCompilationOrchestrator::new();
let header = HeaderUnit::new("<vector>", "/usr/include/c++/vector", true);
orch.add_header_unit(header);
assert_eq!(orch.header_units.len(), 1);
}
#[test]
fn test_orchestrator_compilation_order() {
let mut orch = ModuleCompilationOrchestrator::new();
let mut mod_a = ModuleUnit::new(
ModuleName::from_str("A"),
ModuleUnitKind::ModuleInterface,
"A.cppm",
);
mod_a.add_import(ModuleName::from_str("B"));
let mod_b = ModuleUnit::new(
ModuleName::from_str("B"),
ModuleUnitKind::ModuleInterface,
"B.cppm",
);
orch.add_unit(mod_a);
orch.add_unit(mod_b);
let order = orch.compilation_order();
assert_eq!(order, vec!["B", "A"]);
}
}