use std::collections::BTreeSet;
use super::CallableInfo;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct FrozenCallable {
pub(super) name: String,
pub(super) reason: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum FrozenCause {
ValueReference,
HostEntry,
ManifestHandler,
}
pub(super) struct ValueEscape<'a> {
pub(super) referenced_by_value: &'a BTreeSet<String>,
pub(super) manifest_handlers: &'a BTreeSet<String>,
pub(super) frozen: &'a mut Vec<FrozenCallable>,
}
impl ValueEscape<'_> {
pub(super) fn record(&mut self, info: &CallableInfo) {
let Some(cause) = info.frozen_cause else {
return;
};
if self.frozen.iter().any(|entry| entry.name == info.name) {
return;
}
self.frozen.push(FrozenCallable::new(&info.name, cause));
}
}
impl FrozenCallable {
pub(super) fn new(name: &str, cause: FrozenCause) -> Self {
let reason = match cause {
FrozenCause::ValueReference => format!(
"its value is read as a first-class reference, so it is invoked at its declared arity through a call site the fixer cannot see. It owns the ambient capability use, and a capability it cannot receive cannot be threaded into its body — pass the capability through an existing parameter, or wrap the reference as `{{ args -> {name}(harness, args) }}`"
),
FrozenCause::HostEntry => format!(
"it is declared `@host_entry`, so an embedding host supplies its arguments at the arity it declares. It owns the ambient capability use, and a parameter the host was never asked to pass cannot be introduced — thread the capability through an existing parameter, or have the host pass it and drop `@host_entry` from `{name}`"
),
FrozenCause::ManifestHandler => format!(
"`harn.toml` registers it as a runtime handler, so the runtime supplies its capability argument — and it supplies the root `Harness`, which a narrowed or record carrier cannot receive. Thread the capability through an existing parameter, declare the root handle yourself, or remove the `harn.toml` block that names `{name}`"
),
};
Self {
name: name.to_string(),
reason,
}
}
}