use std::cell::Cell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::engine::error::{TransitionError, UnresolvedKind};
use crate::engine::parse::arena::{Lexeme, NodeStatus};
use crate::engine::Segment;
use crate::semantics::domain::Verdict;
use crate::semantics::evidence::EvidenceStore;
use crate::semantics::Obligations;
use crate::typing::ir::{Instr, Program};
use crate::typing::normalize::{failure_is_stable, unify_modulo, Normalizer};
use crate::typing::pattern::Pattern;
use crate::typing::rule::{PremiseStatus, RuleResult};
use crate::typing::term::{apply, Evidence, Term};
use crate::typing::{Context, ContextTransition, Subst, TyExpr, TypeExpr};
pub type Trees = HashMap<TypeExpr, TyExpr>;
fn instantiate(t: &Term) -> Term {
static NEXT: AtomicU64 = AtomicU64::new(0);
let mut n = NEXT.fetch_add(1 << 20, Ordering::Relaxed);
t.freshen(&mut n)
}
fn rid() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, Debug, Default)]
pub struct Stats {
pub eval: Cell<usize>,
pub ascribe: Cell<usize>,
pub equate: Cell<usize>,
pub member: Cell<usize>,
pub push_scope: Cell<usize>,
pub pop_scope: Cell<usize>,
pub extend: Cell<usize>,
pub emit: Cell<usize>,
pub effect: Cell<usize>,
pub unify: Cell<usize>,
pub unify_fail: Cell<usize>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct StatsSnap {
pub eval: usize,
pub ascribe: usize,
pub equate: usize,
pub member: usize,
pub push_scope: usize,
pub pop_scope: usize,
pub extend: usize,
pub emit: usize,
pub effect: usize,
pub unify: usize,
pub unify_fail: usize,
}
impl StatsSnap {
fn snap(s: &Stats) -> Self {
Self {
eval: s.eval.get(),
ascribe: s.ascribe.get(),
equate: s.equate.get(),
member: s.member.get(),
push_scope: s.push_scope.get(),
pop_scope: s.pop_scope.get(),
extend: s.extend.get(),
emit: s.emit.get(),
effect: s.effect.get(),
unify: s.unify.get(),
unify_fail: s.unify_fail.get(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct TypingDomain<const TRACK: bool = false> {
stats: Stats,
}
impl<const TRACK: bool> TypingDomain<TRACK> {
#[must_use]
pub fn stats(&self) -> StatsSnap {
StatsSnap::snap(&self.stats)
}
pub fn reset_stats(&self) {
self.stats.eval.set(0);
self.stats.ascribe.set(0);
self.stats.equate.set(0);
self.stats.member.set(0);
self.stats.push_scope.set(0);
self.stats.pop_scope.set(0);
self.stats.extend.set(0);
self.stats.emit.set(0);
self.stats.effect.set(0);
self.stats.unify.set(0);
self.stats.unify_fail.set(0);
}
#[inline(always)]
fn tick(cell: &Cell<usize>) {
if TRACK {
cell.set(cell.get() + 1);
}
}
fn ob_resolve<'a>(obligations: &'a Obligations, name: &str) -> Option<&'a Lexeme> {
obligations
.iter()
.find(|o| o.name == name)
.and_then(|o| o.value.as_ref())
}
fn ob_type(obligations: &Obligations, name: &str) -> Option<usize> {
obligations
.iter()
.find(|o| o.name == name)
.and_then(|o| o.evidence)
}
fn eval(
evidence: &EvidenceStore<Evidence>,
ty: &TyExpr,
obligations: &Obligations,
ctx: &Context,
segs: &[Segment],
run: u64,
) -> Option<Term> {
Some(match ty {
TyExpr::Top => Term::top(),
TyExpr::Bot => Term::bottom(),
TyExpr::Lit(s) => Term::Leaf(Pattern::raw(s)),
TyExpr::Var(n) => Term::Var(format!("{n}#{run}")),
TyExpr::Ref(b) => Self::resolve_ref(evidence, obligations, b)?,
TyExpr::Ctx(v) => Self::resolve_ctx(obligations, ctx, segs, v)?,
TyExpr::Inst(v) => instantiate(&Self::resolve_ctx(obligations, ctx, segs, v)?),
TyExpr::Con(label, kids) => {
let mut out = Vec::with_capacity(kids.len());
for k in kids {
out.push(Self::eval(evidence, k, obligations, ctx, segs, run)?);
}
Term::Con(label.clone(), out)
}
})
}
fn resolve_ref(
evidence: &EvidenceStore<Evidence>,
obligations: &Obligations,
b: &str,
) -> Option<Term> {
let id = Self::ob_type(obligations, b)?;
let ev = evidence.get(id)?;
(!ev.is_top()).then_some(ev.term)
}
fn resolve_ctx(
obligations: &Obligations,
ctx: &Context,
segs: &[Segment],
v: &str,
) -> Option<Term> {
let lex = Self::ob_resolve(obligations, v)?;
let text = lex.value(segs).unwrap_or_default();
if let Some(t) = ctx.lookup(&text) {
return Some(t.clone());
}
if lex.open {
return ctx.lookup_starts_with(&text).cloned();
}
None
}
fn ascribe(
&self,
norm: &Normalizer,
actual: &Term,
expected: &Term,
subst: &mut Subst,
open: bool,
) -> PremiseStatus {
let mut s = subst.clone();
Self::tick(&self.stats.unify);
if unify_modulo(norm, expected, actual, &mut s, true) {
*subst = s;
PremiseStatus::Satisfied
} else {
Self::tick(&self.stats.unify_fail);
if open || !failure_is_stable(norm, expected, actual) {
PremiseStatus::Unknown
} else {
PremiseStatus::Contradiction
}
}
}
fn merge_eqs(
&self,
norm: &Normalizer,
evidence: &EvidenceStore<Evidence>,
obligations: &Obligations,
subst: &mut Subst,
) -> PremiseStatus {
let mut st = PremiseStatus::Satisfied;
for ob in obligations {
let Some(ev) = ob.evidence.and_then(|id| evidence.get(id)) else {
continue;
};
for (x, t) in &ev.eqs {
let v = Term::Var(x.clone());
Self::tick(&self.stats.unify);
if !unify_modulo(norm, &v, t, subst, true) {
Self::tick(&self.stats.unify_fail);
if failure_is_stable(norm, &v, t) {
return PremiseStatus::Contradiction;
}
st = PremiseStatus::Unknown;
}
}
}
st
}
fn export(subst: &Subst, run: u64) -> Vec<(String, Term)> {
let suffix = format!("#{run}");
let mut eqs: Vec<(String, Term)> = subst
.keys()
.filter(|k| !k.ends_with(&suffix))
.map(|k| (k.clone(), apply(&Term::Var(k.clone()), subst)))
.collect();
eqs.sort_by(|a, b| a.0.cmp(&b.0));
eqs
}
fn extend(ctx: &Context, value: &str, resolved: Term) -> Context {
ctx.shadow(value.to_string(), resolved)
}
fn top(ctxs: &[Context]) -> &Context {
ctxs.last().expect("context stack is never empty")
}
fn reg(regs: &[Option<Term>], i: usize) -> Option<Term> {
regs.get(i).cloned().flatten()
}
fn set(regs: &mut Vec<Option<Term>>, dst: usize, v: Option<Term>) {
if dst >= regs.len() {
regs.resize(dst + 1, None);
}
regs[dst] = v;
}
#[allow(clippy::too_many_arguments)]
fn eval_to_reg(
&self,
regs: &mut Vec<Option<Term>>,
dst: usize,
expr: &TyExpr,
evidence: &EvidenceStore<Evidence>,
obligations: &Obligations,
ctx: &Context,
segs: &[Segment],
run: u64,
) {
Self::tick(&self.stats.eval);
let v = Self::eval(evidence, expr, obligations, ctx, segs, run);
Self::set(regs, dst, v);
}
fn extend_inputs(
obligations: &Obligations,
regs: &[Option<Term>],
binding: &str,
ty: usize,
segs: &[Segment],
) -> Result<(String, Term), UnresolvedKind> {
let value = Self::ob_resolve(obligations, binding)
.and_then(|lex| lex.value(segs))
.ok_or(UnresolvedKind::Value)?;
let r = Self::reg(regs, ty).ok_or(UnresolvedKind::Type)?;
Ok((value, r))
}
#[allow(clippy::too_many_arguments)]
fn run_ascribe(
&self,
norm: &Normalizer,
evidence: &EvidenceStore<Evidence>,
obligations: &Obligations,
binding: &str,
expected: Option<Term>,
subst: &mut Subst,
allow_missing: bool,
) -> PremiseStatus {
Self::tick(&self.stats.ascribe);
let Some(ob) = obligations.iter().find(|o| o.name.as_str() == binding) else {
return if allow_missing {
PremiseStatus::Unknown
} else {
PremiseStatus::Contradiction
};
};
if ob.value.is_none() {
return PremiseStatus::Unknown;
}
let open = ob.value.as_ref().is_some_and(|v| v.open);
let Some(actual_id) = ob.evidence else {
return if open {
PremiseStatus::Unknown
} else {
PremiseStatus::Contradiction
};
};
let Some(actual) = evidence.get(actual_id) else {
return PremiseStatus::Contradiction;
};
let Some(expected) = expected else {
return PremiseStatus::Unknown;
};
self.ascribe(norm, &actual.term, &expected, subst, open)
}
fn run_member(
&self,
obligations: &Obligations,
ctx: &Context,
binding: &str,
segs: &[Segment],
) -> PremiseStatus {
Self::tick(&self.stats.member);
let Some(lex) = Self::ob_resolve(obligations, binding) else {
return PremiseStatus::Unknown;
};
let text = lex.value(segs).unwrap_or_default();
let exact = !text.is_empty() && ctx.lookup(&text).is_some();
let prefix = lex.open && !text.is_empty() && ctx.lookup_starts_with(&text).is_some();
match (exact || prefix, text.is_empty()) {
(true, _) => PremiseStatus::Satisfied,
(false, true) => PremiseStatus::Unknown,
(false, false) => PremiseStatus::Contradiction,
}
}
#[allow(clippy::too_many_arguments)]
fn run(
&self,
program: &Program,
norm: &Normalizer,
evidence: &EvidenceStore<Evidence>,
obligations: &Obligations,
ctx: Context,
status: NodeStatus,
segs: &[Segment],
) -> RuleResult {
let allow_missing = status.open();
let run = rid();
let mut subst = Subst::new();
let mut regs: Vec<Option<Term>> = Vec::new();
let mut ctxs = vec![ctx];
let mut satisfied = true;
let mut output: Option<Term> = None;
let mut effects: Vec<(String, Term)> = Vec::new();
macro_rules! combine {
($st:expr) => {
match $st {
PremiseStatus::Contradiction => return RuleResult::Contradiction,
PremiseStatus::Unknown => satisfied = false,
PremiseStatus::Satisfied => {}
}
};
}
combine!(self.merge_eqs(norm, evidence, obligations, &mut subst));
for instr in &program.instrs {
match instr {
Instr::Eval { dst, expr } => {
let top = Self::top(&ctxs).clone();
self.eval_to_reg(&mut regs, *dst, expr, evidence, obligations, &top, segs, run);
}
Instr::Ascribe { binding, expected } => {
let exp = Self::reg(®s, *expected);
combine!(self.run_ascribe(
norm,
evidence,
obligations,
binding,
exp,
&mut subst,
allow_missing
));
}
Instr::Equate { left, right } => {
Self::tick(&self.stats.equate);
let st = match (Self::reg(®s, *left), Self::reg(®s, *right)) {
(Some(l), Some(r)) => {
let mut s = subst.clone();
Self::tick(&self.stats.unify);
if unify_modulo(norm, &l, &r, &mut s, true) {
subst = s;
PremiseStatus::Satisfied
} else {
Self::tick(&self.stats.unify_fail);
if failure_is_stable(norm, &l, &r) {
PremiseStatus::Contradiction
} else {
PremiseStatus::Unknown
}
}
}
_ => PremiseStatus::Unknown,
};
combine!(st);
}
Instr::Member { binding } => {
combine!(self.run_member(obligations, Self::top(&ctxs), binding, segs));
}
Instr::PushScope => {
Self::tick(&self.stats.push_scope);
let t = Self::top(&ctxs).clone();
ctxs.push(t);
}
Instr::PopScope => {
Self::tick(&self.stats.pop_scope);
if ctxs.len() > 1 {
ctxs.pop();
}
}
Instr::Extend { binding, ty } => {
Self::tick(&self.stats.extend);
match Self::extend_inputs(obligations, ®s, binding, *ty, segs) {
Ok((v, r)) => {
let top = ctxs.last_mut().expect("context stack is never empty");
*top = top.shadow(v, r);
}
Err(_) => satisfied = false,
}
}
Instr::Emit { ty } => {
Self::tick(&self.stats.emit);
output = Self::reg(®s, *ty);
}
Instr::Effect { binding, ty } => {
Self::tick(&self.stats.effect);
let name = Self::ob_resolve(obligations, binding)
.and_then(|lex| lex.value(segs))
.unwrap_or_else(|| binding.clone());
if let Some(r) = Self::reg(®s, *ty) {
effects.push((name, r));
}
}
}
}
let eqs = Self::export(&subst, run);
let Some(ty) = output else {
return RuleResult::Partial(Evidence {
term: Term::top(),
eqs,
});
};
let ev = Evidence {
term: apply(&ty, &subst),
eqs,
};
if !satisfied {
return RuleResult::Partial(ev);
}
let transforms = effects
.into_iter()
.map(|(n, t)| (n, apply(&t, &subst)))
.collect();
RuleResult::Success((ev, Some(ContextTransition { transforms })))
}
}
impl<const TRACK: bool> TypingDomain<TRACK> {
#[allow(clippy::too_many_arguments)]
pub fn descend(
&self,
program: &Program,
norm: &Normalizer,
binding: Option<&str>,
ctx: &Context,
obligations: &Obligations,
segs: &[Segment],
evidence: &EvidenceStore<Evidence>,
) -> Result<Context, TransitionError> {
let Some(b) = binding else {
return Ok(ctx.clone());
};
let Some(range) = program.splices.get(b).cloned() else {
return Ok(ctx.clone());
};
let run = rid();
let mut regs: Vec<Option<Term>> = Vec::new();
let mut subst = Subst::new();
let _ = self.merge_eqs(norm, evidence, obligations, &mut subst);
for instr in &program.instrs[..range.start] {
match instr {
Instr::Eval { dst, expr } => {
self.eval_to_reg(&mut regs, *dst, expr, evidence, obligations, ctx, segs, run);
}
Instr::Ascribe { binding, expected } => {
if let Some(actual) = Self::resolve_ref(evidence, obligations, binding)
&& let Some(exp) = Self::reg(®s, *expected)
{
let _ = unify_modulo(norm, &exp, &actual, &mut subst, true);
}
}
Instr::Equate { left, right } => {
if let (Some(l), Some(r)) = (Self::reg(®s, *left), Self::reg(®s, *right)) {
let _ = unify_modulo(norm, &l, &r, &mut subst, true);
}
}
_ => {}
}
}
let mut out = ctx.clone();
for instr in &program.instrs[range] {
match instr {
Instr::Eval { dst, expr } => {
self.eval_to_reg(&mut regs, *dst, expr, evidence, obligations, &out, segs, run);
}
Instr::Extend { binding, ty } => {
Self::tick(&self.stats.extend);
let (value, r) = Self::extend_inputs(obligations, ®s, binding, *ty, segs)
.map_err(|kind| TransitionError::Unresolved {
rule: program.name.clone(),
binding: b.to_string(),
setting: binding.clone(),
kind,
})?;
out = out.shadow(value, apply(&r, &subst));
}
_ => {}
}
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub fn finalize(
&self,
program: &Program,
norm: &Normalizer,
ctx: &Context,
obligations: &Obligations,
segs: &[Segment],
status: NodeStatus,
evidence: &EvidenceStore<Evidence>,
) -> (Verdict, Evidence, Option<ContextTransition>) {
match self.run(
program,
norm,
evidence,
obligations,
ctx.clone(),
status,
segs,
) {
RuleResult::Contradiction => (Verdict::Lost, Evidence::top(), None),
RuleResult::Partial(ev) => {
if status.open() {
(Verdict::Live, ev, None)
} else {
(Verdict::Lost, Evidence::top(), None)
}
}
RuleResult::Success((ev, transition)) => {
let effect = if status == NodeStatus::Exact {
transition.filter(|t| !t.transforms.is_empty())
} else {
None
};
(Verdict::Satisfied, ev, effect)
}
}
}
pub fn apply_effect(&self, ctx: Context, effect: &ContextTransition) -> Context {
effect
.transforms
.iter()
.fold(ctx, |acc, (var, ty)| Self::extend(&acc, var, ty.clone()))
}
pub fn compose_effects(&self, effects: &[&ContextTransition]) -> Option<ContextTransition> {
let mut composed = ContextTransition::identity();
for &effect in effects {
composed = composed.compose(effect);
}
(!composed.transforms.is_empty()).then_some(composed)
}
}