use crate::v1::lower::qualify_type_key;
use rustyfi_syntax::cst_v1::{self, ast as ast_v1};
use rustyfi_syntax::leaf::{AnyHorzCmdTok, AnyVertCmdTok, TypeVarTok};
use rustyfi_syntax::span::Span;
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, Default)]
pub(crate) struct ModSurface {
pub(crate) vals: Vec<String>,
pub(crate) types: Vec<(String, usize)>,
pub(crate) mods: Vec<(String, ModSurface)>,
pub(crate) sigs: Vec<String>,
}
#[derive(Clone, Debug)]
pub(crate) struct SigDef<'a> {
pub(crate) decls: &'a [cst_v1::StructDeclV1],
pub(crate) refines: Vec<Refine<'a>>,
#[allow(dead_code)]
pub(crate) def_path: Vec<String>,
}
#[derive(Clone, Debug)]
pub(crate) struct Refine<'a> {
pub(crate) name: String,
pub(crate) path: Vec<String>,
pub(crate) tyvars: &'a [TypeVarTok],
pub(crate) body: &'a cst_v1::TypeBodyV1,
pub(crate) span: Span,
}
#[derive(Clone, Debug)]
pub(crate) struct FunctorDef<'a> {
pub(crate) param: String,
pub(crate) param_sig: &'a ast_v1::SigExpr,
pub(crate) body: &'a ast_v1::ModExpr,
pub(crate) def_path: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct AppResolution {
pub(crate) functor_path: String,
pub(crate) arg_path: String,
}
#[derive(Default)]
pub(crate) struct SurfaceEnv<'a> {
pub(crate) modules: HashMap<String, ModSurface>,
pub(crate) sigs: HashMap<String, SigDef<'a>>,
pub(crate) alias_targets: HashMap<String, Option<String>>,
pub(crate) include_targets: Vec<(String, Span, Option<String>)>,
pub(crate) functors: HashMap<String, FunctorDef<'a>>,
pub(crate) app_targets: Vec<(String, Span, Option<AppResolution>)>,
}
pub(crate) type ParamSubst = Vec<(String, String)>;
fn subst_chain(rendered: &str, subst: &ParamSubst) -> String {
let mut parts = rendered.splitn(2, '.');
let head = parts.next().unwrap_or(rendered);
let rest = parts.next();
for (param, arg) in subst.iter().rev() {
if head == param {
return match rest {
Some(r) => format!("{arg}.{r}"),
None => arg.clone(),
};
}
}
rendered.to_string()
}
fn any_horz_name(cmd: &AnyHorzCmdTok) -> String {
match cmd {
AnyHorzCmdTok::Plain(t) => t.name.clone(),
AnyHorzCmdTok::Mod(t) => t.name.clone(),
}
}
fn any_vert_name(cmd: &AnyVertCmdTok) -> String {
match cmd {
AnyVertCmdTok::Plain(t) => t.name.clone(),
AnyVertCmdTok::Mod(t) => t.name.clone(),
}
}
pub(crate) fn build_file_surface<'a>(file: &'a cst_v1::FileV1, env: &mut SurfaceEnv<'a>) {
if let cst_v1::FileV1::Library {
name,
sig_annot,
binds,
..
} = file
{
let path = vec![name.name.clone()];
let bind_refs: Vec<&cst_v1::Bind> = binds.iter().collect();
let raw = build_binds(&bind_refs, &path, env, &Vec::new());
let filtered = match sig_annot {
Some(sa) => filter_surface(raw, &sa.sig_.0, env, &path),
None => raw,
};
env.modules.insert(path.join("."), filtered);
}
}
fn build_binds<'a>(
binds: &[&'a cst_v1::Bind],
path: &[String],
env: &mut SurfaceEnv<'a>,
subst: &ParamSubst,
) -> ModSurface {
let mut surf = ModSurface::default();
for b in binds.iter().copied() {
match b {
cst_v1::Bind::Value { name, .. } => surf.vals.push(name.name.clone()),
cst_v1::Bind::ValueInline { cmd, .. } => surf.vals.push(any_horz_name(cmd)),
cst_v1::Bind::ValueBlock { cmd, .. } => surf.vals.push(any_vert_name(cmd)),
cst_v1::Bind::ValueMath { cmd, .. } => surf.vals.push(any_horz_name(cmd)),
cst_v1::Bind::ValueRec { first, ands, .. } => {
surf.vals.push(first.name.name.clone());
for a in ands {
surf.vals.push(a.clause.name.name.clone());
}
}
cst_v1::Bind::ValueMutable { name, .. } => surf.vals.push(name.name.clone()),
cst_v1::Bind::Type { first, ands, .. } => {
surf.types
.push((first.name.name.clone(), first.tyvars.len()));
for a in ands {
surf.types
.push((a.bind.name.name.clone(), a.bind.tyvars.len()));
}
}
cst_v1::Bind::Module {
name,
sig_annot: _,
body,
..
} if matches!(&*body.0, ast_v1::ModExpr::Functor { .. }) => {
let ast_v1::ModExpr::Functor {
param: fparam,
dom,
body: fbody,
..
} = &*body.0
else {
unreachable!("guarded by the arm's own `matches!` pattern")
};
let mut child_path = path.to_vec();
child_path.push(name.name.clone());
env.functors.insert(
child_path.join("."),
FunctorDef {
param: fparam.name.clone(),
param_sig: dom.as_ref(),
body: fbody.as_ref(),
def_path: path.to_vec(),
},
);
}
cst_v1::Bind::Module {
name,
sig_annot,
body,
..
} => {
let mut child_path = path.to_vec();
child_path.push(name.name.clone());
let mut alias_target_path: Option<String> = None;
let base_surf = match &*body.0 {
ast_v1::ModExpr::Struct { binds: inner, .. } => {
let inner_binds: Vec<&cst_v1::Bind> =
inner.iter().map(|sb| sb.0.as_ref()).collect();
build_binds(&inner_binds, &child_path, env, subst)
}
ast_v1::ModExpr::Var(chain) => {
let resolved =
resolve_module(env, path, &subst_chain(&chain.render(), subst))
.map(|(t, s)| (t, s.clone()));
alias_target_path = resolved.as_ref().map(|(t, _)| t.clone());
env.alias_targets
.insert(child_path.join("."), alias_target_path.clone());
resolved.map(|(_, s)| s).unwrap_or_default()
}
ast_v1::ModExpr::Coerce {
name: target, sig_, ..
} => {
let resolved = resolve_module(env, path, &subst_chain(&target.name, subst))
.map(|(t, s)| (t, s.clone()));
alias_target_path = resolved.as_ref().map(|(t, _)| t.clone());
env.alias_targets
.insert(child_path.join("."), alias_target_path.clone());
let target_surf = resolved.map(|(_, s)| s).unwrap_or_default();
filter_surface(target_surf, sig_, env, &child_path)
}
ast_v1::ModExpr::App { func, arg } => {
build_app_result_surface(env, path, &child_path, func, arg, subst)
}
ast_v1::ModExpr::Functor { .. } => {
unreachable!(
"a functor-bodied Bind::Module is intercepted by the guarded arm above"
)
}
};
let filtered = match sig_annot {
Some(sa) => filter_surface(base_surf, &sa.sig_.0, env, &child_path),
None => base_surf,
};
if let Some(target_path) = &alias_target_path {
register_sig_reexports(env, &child_path, target_path, &filtered);
}
env.modules.insert(child_path.join("."), filtered.clone());
surf.mods.push((name.name.clone(), filtered));
}
cst_v1::Bind::Signature { name, sig_, .. } => {
let key = qualify_type_key(path, &name.name);
if let Some((decls, refines)) = resolve_sig_rhs(&sig_.0, env, path) {
env.sigs.insert(
key,
SigDef {
decls,
refines,
def_path: path.to_vec(),
},
);
}
surf.sigs.push(name.name.clone());
}
cst_v1::Bind::Include { kw, body } => match &*body.0 {
ast_v1::ModExpr::Var(chain) => {
let resolved = resolve_module(env, path, &subst_chain(&chain.render(), subst))
.map(|(t, s)| (t, s.clone()));
let target_path = resolved.as_ref().map(|(t, _)| t.clone());
env.include_targets
.push((path.join("."), kw.0, target_path));
if let Some((target_path, target_surf)) = resolved {
register_sig_reexports(env, path, &target_path, &target_surf);
surf.vals.extend(target_surf.vals);
surf.types.extend(target_surf.types);
surf.mods.extend(target_surf.mods);
surf.sigs.extend(target_surf.sigs);
}
}
ast_v1::ModExpr::App { func, arg } => {
let result_surf = build_app_result_surface(env, path, path, func, arg, subst);
surf.vals.extend(result_surf.vals);
surf.types.extend(result_surf.types);
surf.mods.extend(result_surf.mods);
surf.sigs.extend(result_surf.sigs);
}
ast_v1::ModExpr::Struct { .. }
| ast_v1::ModExpr::Coerce { .. }
| ast_v1::ModExpr::Functor { .. } => {}
},
}
}
surf
}
fn mod_chain_span(c: &ast_v1::ModChainV1) -> Span {
match c {
ast_v1::ModChainV1::Long(t) => t.span,
ast_v1::ModChainV1::Single(t) => t.span,
}
}
fn build_app_result_surface<'a>(
env: &mut SurfaceEnv<'a>,
enclosing_path: &[String],
result_path: &[String],
func: &ast_v1::ModChainV1,
arg: &ast_v1::ModChainV1,
subst: &ParamSubst,
) -> ModSurface {
let functor_resolved: Option<(String, FunctorDef<'a>)> =
resolve_functor(env, enclosing_path, &subst_chain(&func.render(), subst))
.map(|(p, f)| (p, f.clone()));
let arg_resolved: Option<(String, ModSurface)> =
resolve_module(env, enclosing_path, &subst_chain(&arg.render(), subst))
.map(|(p, s)| (p, s.clone()));
let app_span = mod_chain_span(func);
let body_binds: Option<&[cst_v1::StructBindV1]> = functor_resolved
.as_ref()
.and_then(|(_, fdef)| crate::v1::functor::functor_body_binds(fdef.body));
let resolution = match (&functor_resolved, &arg_resolved, &body_binds) {
(Some((fpath, _)), Some((apath, _)), Some(_)) => Some(AppResolution {
functor_path: fpath.clone(),
arg_path: apath.clone(),
}),
_ => None,
};
env.app_targets
.push((enclosing_path.join("."), app_span, resolution.clone()));
match (body_binds, &functor_resolved, &arg_resolved) {
(Some(binds), Some((_, fdef)), Some((apath, _))) if resolution.is_some() => {
let bind_refs: Vec<&cst_v1::Bind> = binds.iter().map(|sb| sb.0.as_ref()).collect();
let mut body_subst = subst.clone();
body_subst.push((fdef.param.clone(), apath.clone()));
build_binds(&bind_refs, result_path, env, &body_subst)
}
_ => ModSurface::default(),
}
}
pub(crate) fn resolve_functor<'a, 'b>(
env: &'b SurfaceEnv<'a>,
site_path: &[String],
chain: &str,
) -> Option<(String, &'b FunctorDef<'a>)> {
for candidate in outward_candidates(site_path, chain) {
if let Some(f) = env.functors.get(&candidate) {
return Some((candidate, f));
}
}
None
}
pub(crate) fn frozen_app_target<'a, 'b>(
env: &'b SurfaceEnv<'a>,
enclosing_path: &[String],
span: Span,
) -> Option<&'b Option<AppResolution>> {
let key = enclosing_path.join(".");
env.app_targets
.iter()
.find(|(p, s, _)| *p == key && *s == span)
.map(|(_, _, t)| t)
}
fn joined(mods: &[String], name: &str) -> String {
if mods.is_empty() {
name.to_string()
} else {
format!("{}.{}", mods.join("."), name)
}
}
fn sig_bot_decls<'a>(
bot: &'a ast_v1::SigBotV1,
env: &SurfaceEnv<'a>,
site_path: &[String],
) -> Option<(&'a [cst_v1::StructDeclV1], Vec<Refine<'a>>)> {
match bot {
ast_v1::SigBotV1::Sig { decls, .. } => Some((decls.as_slice(), Vec::new())),
ast_v1::SigBotV1::Var(t) => {
find_sig(env, site_path, &t.name).map(|d| (d.decls, d.refines.clone()))
}
ast_v1::SigBotV1::Path(t) => {
let suffix = joined(&t.mods, &t.name);
find_sig(env, site_path, &suffix).map(|d| (d.decls, d.refines.clone()))
}
}
}
fn resolve_sig_rhs<'a>(
sig: &'a ast_v1::SigExpr,
env: &SurfaceEnv<'a>,
site_path: &[String],
) -> Option<(&'a [cst_v1::StructDeclV1], Vec<Refine<'a>>)> {
match sig {
ast_v1::SigExpr::Bot(bot) => sig_bot_decls(bot, env, site_path),
ast_v1::SigExpr::WithType {
base, path, binds, ..
} => {
let (decls, mut refines) = sig_bot_decls(base, env, site_path)?;
refines.extend(collect_refines(binds, mod_chain_segments(path)));
Some((decls, refines))
}
ast_v1::SigExpr::Functor { .. } => None,
}
}
pub(crate) fn mod_chain_segments(path: &Option<ast_v1::ModChainV1>) -> Vec<String> {
match path {
None => Vec::new(),
Some(ast_v1::ModChainV1::Single(t)) => vec![t.name.clone()],
Some(ast_v1::ModChainV1::Long(t)) => {
let mut segs = t.mods.clone();
segs.push(t.name.clone());
segs
}
}
}
pub(crate) fn collect_refines(
binds: &cst_v1::TypeBindsErasedV1,
path: Vec<String>,
) -> Vec<Refine<'_>> {
let inner = &binds.0;
let mut out = vec![refine_from_single(&inner.first, path.clone())];
for a in &inner.ands {
out.push(refine_from_single(&a.bind, path.clone()));
}
out
}
fn refine_from_single(single: &cst_v1::TypeBindSingleV1, path: Vec<String>) -> Refine<'_> {
Refine {
name: single.name.name.clone(),
path,
tyvars: &single.tyvars,
body: &single.body,
span: single.name.span,
}
}
fn sig_decl_views<'a>(
sig: &'a ast_v1::SigExpr,
env: &SurfaceEnv<'a>,
site_path: &[String],
) -> Option<Vec<&'a cst_v1::StructDeclV1>> {
let mut visited = Vec::new();
sig_decl_views_visited(sig, env, site_path, &mut visited)
}
fn sig_decl_views_visited<'a>(
sig: &'a ast_v1::SigExpr,
env: &SurfaceEnv<'a>,
site_path: &[String],
visited: &mut Vec<String>,
) -> Option<Vec<&'a cst_v1::StructDeclV1>> {
match sig {
ast_v1::SigExpr::Bot(bot) => sig_bot_decl_views(bot, env, site_path, visited),
ast_v1::SigExpr::WithType { base, .. } => sig_bot_decl_views(base, env, site_path, visited),
ast_v1::SigExpr::Functor { .. } => None,
}
}
fn sig_bot_decl_views<'a>(
bot: &'a ast_v1::SigBotV1,
env: &SurfaceEnv<'a>,
site_path: &[String],
visited: &mut Vec<String>,
) -> Option<Vec<&'a cst_v1::StructDeclV1>> {
match bot {
ast_v1::SigBotV1::Sig { decls, .. } => splice_decl_views(decls, env, site_path, visited),
ast_v1::SigBotV1::Var(t) => named_sig_decl_views(&t.name, env, site_path, visited),
ast_v1::SigBotV1::Path(t) => {
let suffix = joined(&t.mods, &t.name);
named_sig_decl_views(&suffix, env, site_path, visited)
}
}
}
fn named_sig_decl_views<'a>(
name: &str,
env: &SurfaceEnv<'a>,
site_path: &[String],
visited: &mut Vec<String>,
) -> Option<Vec<&'a cst_v1::StructDeclV1>> {
let (key, def) = find_sig_keyed(env, site_path, name)?;
if visited.contains(&key) {
return None;
}
visited.push(key);
let out = splice_decl_views(def.decls, env, site_path, visited);
visited.pop();
out
}
fn splice_decl_views<'a>(
decls: &'a [cst_v1::StructDeclV1],
env: &SurfaceEnv<'a>,
site_path: &[String],
visited: &mut Vec<String>,
) -> Option<Vec<&'a cst_v1::StructDeclV1>> {
let mut out = Vec::new();
for d in decls {
if let ast_v1::Decl::Include { sig_, .. } = &*d.0 {
out.extend(sig_decl_views_visited(sig_, env, site_path, visited)?);
} else {
out.push(d);
}
}
Some(out)
}
fn filter_surface<'a>(
raw: ModSurface,
sig: &'a ast_v1::SigExpr,
env: &SurfaceEnv<'a>,
site_path: &[String],
) -> ModSurface {
let Some(decls) = sig_decl_views(sig, env, site_path) else {
return raw;
};
let mut dv: HashSet<String> = HashSet::new();
let mut dt: HashSet<String> = HashSet::new();
let mut dm: HashSet<String> = HashSet::new();
let mut ds: HashSet<String> = HashSet::new();
for d in decls {
match &*d.0 {
ast_v1::Decl::Val { name, .. } => {
dv.insert(name.name.clone());
}
ast_v1::Decl::ValHorzCmd { cmd, .. } => {
dv.insert(cmd.name.clone());
}
ast_v1::Decl::ValVertCmd { cmd, .. } => {
dv.insert(cmd.name.clone());
}
ast_v1::Decl::TypeOpaque { name, .. } => {
dt.insert(name.name.clone());
}
ast_v1::Decl::Type { binds, .. } => {
dt.insert(binds.0.first.name.name.clone());
for a in &binds.0.ands {
dt.insert(a.bind.name.name.clone());
}
}
ast_v1::Decl::Module { name, .. } => {
dm.insert(name.name.clone());
}
ast_v1::Decl::Signature { name, .. } => {
ds.insert(name.name.clone());
}
ast_v1::Decl::Include { .. } => {}
}
}
ModSurface {
vals: raw.vals.into_iter().filter(|v| dv.contains(v)).collect(),
types: raw
.types
.into_iter()
.filter(|(n, _)| dt.contains(n))
.collect(),
mods: raw
.mods
.into_iter()
.filter(|(n, _)| dm.contains(n))
.collect(),
sigs: raw.sigs.into_iter().filter(|n| ds.contains(n)).collect(),
}
}
fn outward_candidates(site_path: &[String], suffix: &str) -> Vec<String> {
let mut out = Vec::with_capacity(site_path.len() + 1);
for i in (0..=site_path.len()).rev() {
if i == 0 {
out.push(suffix.to_string());
} else {
out.push(format!("{}.{}", site_path[..i].join("."), suffix));
}
}
out
}
pub(crate) fn resolve_module<'a, 'b>(
env: &'b SurfaceEnv<'a>,
site_path: &[String],
chain: &str,
) -> Option<(String, &'b ModSurface)> {
for candidate in outward_candidates(site_path, chain) {
if let Some(s) = env.modules.get(&candidate) {
return Some((candidate, s));
}
}
None
}
pub(crate) fn frozen_alias_target<'a, 'b>(
env: &'b SurfaceEnv<'a>,
alias_path: &[String],
) -> Option<&'b Option<String>> {
env.alias_targets.get(&alias_path.join("."))
}
pub(crate) fn frozen_include_target<'a, 'b>(
env: &'b SurfaceEnv<'a>,
includer_path: &[String],
kw_span: Span,
) -> Option<&'b Option<String>> {
let key = includer_path.join(".");
env.include_targets
.iter()
.find(|(p, s, _)| *p == key && *s == kw_span)
.map(|(_, _, t)| t)
}
fn register_sig_reexports<'a>(
env: &mut SurfaceEnv<'a>,
at_path: &[String],
target_path: &str,
surface: &ModSurface,
) {
for s in &surface.sigs {
let target_key = format!("{target_path}.{s}");
if let Some(def) = env.sigs.get(&target_key).cloned() {
env.sigs.insert(qualify_type_key(at_path, s), def);
}
}
}
pub(crate) fn find_sig<'a, 'b>(
env: &'b SurfaceEnv<'a>,
site_path: &[String],
suffix: &str,
) -> Option<&'b SigDef<'a>> {
find_sig_keyed(env, site_path, suffix).map(|(_, d)| d)
}
pub(crate) fn find_sig_keyed<'a, 'b>(
env: &'b SurfaceEnv<'a>,
site_path: &[String],
suffix: &str,
) -> Option<(String, &'b SigDef<'a>)> {
for candidate in outward_candidates(site_path, suffix) {
if let Some(d) = env.sigs.get(&candidate) {
return Some((candidate, d));
}
}
None
}
pub(crate) fn sig_path_suffix(mods: &[String], name: &str) -> String {
joined(mods, name)
}
#[cfg(test)]
mod tests {
use super::*;
use rustyfi_syntax::parse_file_v1;
fn parse(src: &str) -> cst_v1::FileV1 {
parse_file_v1(src).unwrap_or_else(|e| panic!("v1 parse failed: {e}"))
}
#[test]
fn plain_struct_surface_lists_every_member() {
let file = parse(
"module Lib = struct\n\
val x = 1\n\
val f y = y\n\
type t = int\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("Lib").expect("Lib registered");
assert_eq!(surf.vals, vec!["x".to_string(), "f".to_string()]);
assert_eq!(surf.types, vec![("t".to_string(), 0)]);
}
#[test]
fn sealed_surface_is_filtered_to_declared_names() {
let file = parse(
"module Lib :> sig val x : int end = struct\n\
val x = 1\n\
val secret = 2\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("Lib").expect("Lib registered");
assert_eq!(surf.vals, vec!["x".to_string()]);
}
#[test]
fn unfilterable_sig_passes_the_full_surface_through() {
let file = parse(
"module Lib :> sig include S end = struct\n\
val x = 1\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("Lib").expect("Lib registered");
assert_eq!(surf.vals, vec!["x".to_string()]);
}
#[test]
fn nested_module_resolves_outward_before_falling_back_to_bare() {
let file = parse(
"module Lib = struct\n\
module Inner = struct val y = 2 end\n\
module Alias = Inner\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let (resolved, surf) =
resolve_module(&env, &["Lib".to_string()], "Inner").expect("Inner resolves");
assert_eq!(resolved, "Lib.Inner");
assert_eq!(surf.vals, vec!["y".to_string()]);
let alias = env.modules.get("Lib.Alias").expect("Alias registered");
assert_eq!(alias.vals, vec!["y".to_string()]);
}
#[test]
fn named_signature_registers_and_resolves_by_qualified_key() {
let file = parse(
"module Lib = struct\n\
signature S = sig val x : int end\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let def = find_sig(&env, &["Lib".to_string()], "S").expect("S resolves");
assert_eq!(def.decls.len(), 1);
}
#[test]
fn include_splices_target_surface_at_its_source_position() {
let file = parse(
"module P = struct\n\
module Base = struct val x = 1 val f y = y type t = int end\n\
include Base\n\
val extra = 1\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("P").expect("P registered");
assert_eq!(
surf.vals,
vec!["x".to_string(), "f".to_string(), "extra".to_string()],
"spliced vals sit BEFORE `extra`, matching source order"
);
assert_eq!(surf.types, vec![("t".to_string(), 0)]);
}
#[test]
fn include_of_an_unknown_target_freezes_a_none_resolution() {
let file = parse("module P = struct\ninclude Nope\nend");
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("P").expect("P registered");
assert!(
surf.vals.is_empty(),
"an unresolved include splices nothing"
);
assert_eq!(
env.include_targets.len(),
1,
"the miss is FROZEN, not merely absent"
);
let (path, kw_span, target) = &env.include_targets[0];
assert_eq!(path, "P");
assert_eq!(target, &None);
assert_eq!(
frozen_include_target(&env, &["P".to_string()], *kw_span),
Some(&None)
);
}
#[test]
fn include_reexports_the_target_named_signature() {
let file = parse(
"module P = struct\n\
module Basic = struct\n\
signature Ord = sig type t :: o end\n\
end\n\
include Basic\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let def = find_sig(&env, &["P".to_string()], "Ord").expect("P.Ord resolves");
assert_eq!(def.decls.len(), 1);
}
#[test]
fn include_of_a_sealed_target_only_splices_its_declared_members() {
let file = parse(
"module P = struct\n\
module Base :> sig val x : int end = struct\n\
val x = 1\n\
val secret = 2\n\
end\n\
include Base\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let surf = env.modules.get("P").expect("P registered");
assert_eq!(surf.vals, vec!["x".to_string()]);
}
#[test]
fn alias_reexports_the_target_named_signature() {
let file = parse(
"module Lib = struct\n\
module Basic = struct\n\
signature Ord = sig type t :: o end\n\
end\n\
module A2 = Basic\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let def = find_sig(&env, &["Lib".to_string()], "A2.Ord").expect("Lib.A2.Ord resolves");
assert_eq!(def.decls.len(), 1);
}
#[test]
fn functor_def_registers_no_member_and_application_computes_the_result_surface() {
let file = parse(
"module M = struct\n\
module A = struct val a = 1 end\n\
module F = fun (X : S) -> struct val y = X.a end\n\
module R = F A\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
assert!(
env.functors.contains_key("M.F"),
"M.F must be a registered functor"
);
assert!(
!env.modules.contains_key("M.F"),
"a functor name is never a usable module"
);
let r = env.modules.get("M.R").expect("M.R registered");
assert_eq!(r.vals, vec!["y".to_string()]);
}
#[test]
fn application_of_an_unknown_functor_freezes_a_failed_resolution() {
let file = parse("module M = struct\nmodule R = Unknown A\nend");
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let r = env
.modules
.get("M.R")
.expect("M.R still registered, just empty");
assert!(r.vals.is_empty() && r.mods.is_empty());
assert_eq!(
env.app_targets.len(),
1,
"the miss is FROZEN, not merely absent"
);
let (path, span, _) = &env.app_targets[0];
assert_eq!(path, "M");
assert_eq!(
frozen_app_target(&env, &["M".to_string()], *span),
Some(&None)
);
}
#[test]
fn application_whose_argument_is_the_enclosing_parameter_resolves_through_the_subst_stack() {
let file = parse(
"module M = struct\n\
module F = fun (Y : S2) -> struct val g y = y end\n\
module Base = struct end\n\
module Outer = fun (X : S) -> struct module R = F X end\n\
module Applied = Outer Base\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
let (path, span, resolution) = env
.app_targets
.iter()
.find(|(p, _, _)| p == "M.Applied")
.expect("the inner `F X` application (inside Outer's instantiated body) is frozen");
assert_eq!(
resolution,
&Some(AppResolution {
functor_path: "M.F".to_string(),
arg_path: "M.Base".to_string()
}),
"F X's argument X (Outer's own parameter) substitutes to Outer's application's \
argument, M.Base"
);
assert_eq!(
frozen_app_target(&env, &["M".to_string(), "Applied".to_string()], *span),
Some(&Some(AppResolution {
functor_path: "M.F".to_string(),
arg_path: "M.Base".to_string()
}))
);
let _ = path;
}
#[test]
fn application_whose_argument_is_the_enclosing_parameter_stays_unresolved_when_never_applied() {
let file = parse(
"module M = struct\n\
module F = fun (Y : S2) -> struct val g y = y end\n\
module Outer = fun (X : S) -> struct module R = F X end\n\
end",
);
let mut env = SurfaceEnv::default();
build_file_surface(&file, &mut env);
assert!(
env.app_targets.is_empty(),
"Outer's body is never walked at all unless Outer itself is applied"
);
}
}