use std::collections::{BTreeMap, HashSet};
use intern_lang::Interner;
use module_lang::{ModuleGraph, ModuleId, ResolveError, Symbol, Visibility};
use proptest::prelude::*;
use source_lang::SourceMap;
const MODULES: usize = 5;
const NAMES: usize = 5;
#[derive(Clone, Debug)]
enum Op {
Define {
module: usize,
name: usize,
public: bool,
value: u32,
},
Import {
into: usize,
from: usize,
name: usize,
},
}
#[derive(Clone, Debug)]
enum RefEntry {
Define { public: bool, value: u32 },
Import { from: usize },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Class {
Found(u32),
Unresolved,
Private,
Cycle,
}
fn ref_resolve(model: &[BTreeMap<usize, RefEntry>], module: usize, name: usize) -> Class {
match model[module].get(&name) {
None => Class::Unresolved,
Some(RefEntry::Define { value, .. }) => Class::Found(*value),
Some(RefEntry::Import { from }) => {
let mut visited = HashSet::new();
let _ = visited.insert((module, name));
ref_export(model, *from, name, &mut visited)
}
}
}
fn ref_export(
model: &[BTreeMap<usize, RefEntry>],
module: usize,
name: usize,
visited: &mut HashSet<(usize, usize)>,
) -> Class {
if !visited.insert((module, name)) {
return Class::Cycle;
}
match model[module].get(&name) {
None => Class::Unresolved,
Some(RefEntry::Define {
public: true,
value,
}) => Class::Found(*value),
Some(RefEntry::Define { public: false, .. }) => Class::Private,
Some(RefEntry::Import { from }) => ref_export(model, *from, name, visited),
}
}
fn real_resolve(graph: &ModuleGraph<u32>, module: ModuleId, name: Symbol) -> Class {
match graph.resolve(module, name) {
Ok(value) => Class::Found(*value),
Err(ResolveError::Unresolved { .. }) => Class::Unresolved,
Err(ResolveError::Private { .. }) => Class::Private,
Err(ResolveError::ImportCycle { .. }) => Class::Cycle,
Err(other) => panic!("resolve produced an unexpected error: {other:?}"),
}
}
fn op_strategy() -> impl Strategy<Value = Op> {
prop_oneof![
(0..MODULES, 0..NAMES, any::<bool>(), any::<u32>()).prop_map(
|(module, name, public, value)| Op::Define {
module,
name,
public,
value,
}
),
(0..MODULES, 0..MODULES, 0..NAMES).prop_map(|(into, from, name)| Op::Import {
into,
from,
name
}),
]
}
proptest! {
#[test]
fn resolution_matches_naive_reference(ops in prop::collection::vec(op_strategy(), 0..40)) {
let mut interner = Interner::new();
let names: Vec<Symbol> = (0..NAMES).map(|i| interner.intern(&format!("n{i}"))).collect();
let mut sources = SourceMap::new();
let mut graph: ModuleGraph<u32> = ModuleGraph::new();
let ids: Vec<ModuleId> = (0..MODULES)
.map(|i| {
let src = sources.add(format!("m{i}"), "").expect("source fits");
graph.add_module(interner.intern(&format!("mod{i}")), src)
})
.collect();
let mut model: Vec<BTreeMap<usize, RefEntry>> = vec![BTreeMap::new(); MODULES];
for op in ops {
match op {
Op::Define { module, name, public, value } => {
let absent = !model[module].contains_key(&name);
let vis = if public { Visibility::Public } else { Visibility::Private };
let result = graph.define(ids[module], names[name], vis, value);
prop_assert_eq!(result.is_ok(), absent);
if absent {
let _ = model[module].insert(name, RefEntry::Define { public, value });
}
}
Op::Import { into, from, name } => {
let absent = !model[into].contains_key(&name);
let result = graph.import(ids[into], ids[from], names[name]);
prop_assert_eq!(result.is_ok(), absent);
if absent {
let _ = model[into].insert(name, RefEntry::Import { from });
}
}
}
}
for (index, id) in ids.iter().enumerate() {
prop_assert_eq!(id.to_u32() as usize, index);
}
for (module, &module_id) in ids.iter().enumerate() {
for (name, &name_sym) in names.iter().enumerate() {
let expected = ref_resolve(&model, module, name);
let actual = real_resolve(&graph, module_id, name_sym);
prop_assert_eq!(actual, expected, "module {} name {}", module, name);
prop_assert_eq!(real_resolve(&graph, module_id, name_sym), actual);
}
}
}
}
#[test]
fn test_long_reexport_chain_resolves_to_origin() {
const DEPTH: usize = 4_096;
let mut interner = Interner::new();
let mut sources = SourceMap::new();
let mut graph: ModuleGraph<u32> = ModuleGraph::new();
let item = interner.intern("item");
let ids: Vec<ModuleId> = (0..DEPTH)
.map(|i| {
let src = sources.add(format!("m{i}"), "").expect("source fits");
graph.add_module(interner.intern(&format!("mod{i}")), src)
})
.collect();
graph
.define(ids[DEPTH - 1], item, Visibility::Public, 777)
.expect("unique");
for i in 0..DEPTH - 1 {
graph.import(ids[i], ids[i + 1], item).expect("unique");
}
assert_eq!(graph.resolve(ids[0], item), Ok(&777));
}