use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum ResourceKind {
WeaveHandle,
Channel,
}
impl ResourceKind {
pub(super) fn name(&self) -> &'static str {
match self {
ResourceKind::WeaveHandle => "WeaveHandle",
ResourceKind::Channel => "Channel",
}
}
pub(super) fn cleanup_suggestion(&self) -> &'static str {
match self {
ResourceKind::WeaveHandle => "use `strand.weave-cancel` or resume to completion",
ResourceKind::Channel => "use `chan.close` when done",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TrackedResource {
pub kind: ResourceKind,
pub id: usize,
pub created_line: usize,
pub created_by: String,
}
#[derive(Debug, Clone)]
pub(crate) enum StackValue {
Resource(TrackedResource),
Unknown,
}
#[derive(Debug, Clone)]
pub(crate) struct StackState {
pub(super) stack: Vec<StackValue>,
pub(super) aux_stack: Vec<StackValue>,
pub(super) consumed: Vec<TrackedResource>,
pub(super) next_id: usize,
}
impl Default for StackState {
fn default() -> Self {
Self::new()
}
}
impl StackState {
pub fn new() -> Self {
StackState {
stack: Vec::new(),
aux_stack: Vec::new(),
consumed: Vec::new(),
next_id: 0,
}
}
pub fn push_unknown(&mut self) {
self.stack.push(StackValue::Unknown);
}
pub fn push_resource(&mut self, kind: ResourceKind, line: usize, word: &str) {
let resource = TrackedResource {
kind,
id: self.next_id,
created_line: line,
created_by: word.to_string(),
};
self.next_id += 1;
self.stack.push(StackValue::Resource(resource));
}
pub fn pop(&mut self) -> Option<StackValue> {
self.stack.pop()
}
pub fn peek(&self) -> Option<&StackValue> {
self.stack.last()
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn consume_resource(&mut self, resource: TrackedResource) {
self.consumed.push(resource);
}
pub fn remaining_resources(&self) -> Vec<&TrackedResource> {
self.stack
.iter()
.filter_map(|v| match v {
StackValue::Resource(r) => Some(r),
StackValue::Unknown => None,
})
.collect()
}
pub fn merge(&self, other: &StackState) -> BranchMergeResult {
let self_resources: HashMap<usize, &TrackedResource> = self
.stack
.iter()
.filter_map(|v| match v {
StackValue::Resource(r) => Some((r.id, r)),
StackValue::Unknown => None,
})
.collect();
let other_resources: HashMap<usize, &TrackedResource> = other
.stack
.iter()
.filter_map(|v| match v {
StackValue::Resource(r) => Some((r.id, r)),
StackValue::Unknown => None,
})
.collect();
let self_consumed: std::collections::HashSet<usize> =
self.consumed.iter().map(|r| r.id).collect();
let other_consumed: std::collections::HashSet<usize> =
other.consumed.iter().map(|r| r.id).collect();
let mut inconsistent = Vec::new();
for (id, resource) in &self_resources {
if other_consumed.contains(id) && !self_consumed.contains(id) {
inconsistent.push(InconsistentResource {
resource: (*resource).clone(),
consumed_in_else: true,
});
}
}
for (id, resource) in &other_resources {
if self_consumed.contains(id) && !other_consumed.contains(id) {
inconsistent.push(InconsistentResource {
resource: (*resource).clone(),
consumed_in_else: false,
});
}
}
BranchMergeResult { inconsistent }
}
pub fn join(&self, other: &StackState) -> StackState {
let other_consumed: std::collections::HashSet<usize> =
other.consumed.iter().map(|r| r.id).collect();
let definitely_consumed: Vec<TrackedResource> = self
.consumed
.iter()
.filter(|r| other_consumed.contains(&r.id))
.cloned()
.collect();
let mut joined_stack = self.stack.clone();
let other_resources: HashMap<usize, TrackedResource> = other
.stack
.iter()
.filter_map(|v| match v {
StackValue::Resource(r) => Some((r.id, r.clone())),
StackValue::Unknown => None,
})
.collect();
for (i, val) in joined_stack.iter_mut().enumerate() {
if matches!(val, StackValue::Unknown)
&& i < other.stack.len()
&& let StackValue::Resource(r) = &other.stack[i]
{
*val = StackValue::Resource(r.clone());
}
}
let self_resource_ids: std::collections::HashSet<usize> = joined_stack
.iter()
.filter_map(|v| match v {
StackValue::Resource(r) => Some(r.id),
StackValue::Unknown => None,
})
.collect();
for (id, resource) in other_resources {
if !self_resource_ids.contains(&id) && !definitely_consumed.iter().any(|r| r.id == id) {
joined_stack.push(StackValue::Resource(resource));
}
}
let joined_aux = if self.aux_stack.len() >= other.aux_stack.len() {
self.aux_stack.clone()
} else {
other.aux_stack.clone()
};
StackState {
stack: joined_stack,
aux_stack: joined_aux,
consumed: definitely_consumed,
next_id: self.next_id.max(other.next_id),
}
}
}
#[derive(Debug)]
pub(crate) struct BranchMergeResult {
pub inconsistent: Vec<InconsistentResource>,
}
#[derive(Debug)]
pub(crate) struct InconsistentResource {
pub resource: TrackedResource,
pub consumed_in_else: bool,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct WordResourceInfo {
pub returns: Vec<ResourceKind>,
}