use super::var_id::VarId;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BorrowKind {
Shared,
Mutable,
}
impl BorrowKind {
pub fn conflicts_with(&self, other: BorrowKind) -> bool {
matches!(
(self, other),
(BorrowKind::Mutable, _) | (_, BorrowKind::Mutable)
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum BorrowStateV2 {
#[default]
Owned,
SharedRef {
source: VarId,
start_line: u32,
},
MutRef {
source: VarId,
start_line: u32,
},
Moved {
to: VarId,
at_line: u32,
},
Dropped {
at_line: u32,
},
Copied {
to: VarId,
at_line: u32,
},
}
impl BorrowStateV2 {
pub fn can_read(&self) -> bool {
matches!(
self,
BorrowStateV2::Owned
| BorrowStateV2::SharedRef { .. }
| BorrowStateV2::MutRef { .. }
| BorrowStateV2::Copied { .. }
)
}
pub fn can_mutate(&self) -> bool {
matches!(self, BorrowStateV2::Owned | BorrowStateV2::MutRef { .. })
}
pub fn is_invalidated(&self) -> bool {
matches!(
self,
BorrowStateV2::Moved { .. } | BorrowStateV2::Dropped { .. }
)
}
pub fn is_reference(&self) -> bool {
matches!(
self,
BorrowStateV2::SharedRef { .. } | BorrowStateV2::MutRef { .. }
)
}
pub fn borrow_source(&self) -> Option<VarId> {
match self {
BorrowStateV2::SharedRef { source, .. } | BorrowStateV2::MutRef { source, .. } => {
Some(*source)
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActiveBorrowV2 {
pub borrower: VarId,
pub kind: BorrowKind,
pub start_line: u32,
pub end_line: Option<u32>,
}
impl ActiveBorrowV2 {
pub fn new(borrower: VarId, kind: BorrowKind, start_line: u32) -> Self {
Self {
borrower,
kind,
start_line,
end_line: None,
}
}
pub fn is_active_at(&self, line: u32) -> bool {
line >= self.start_line && self.end_line.is_none_or(|end| line < end)
}
pub fn end_at(&mut self, line: u32) {
self.end_line = Some(line);
}
pub fn conflicts_with(&self, new_kind: BorrowKind, at_line: u32) -> bool {
self.is_active_at(at_line) && self.kind.conflicts_with(new_kind)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BorrowConflict {
pub variable: VarId,
pub existing: ActiveBorrowV2,
pub new_kind: BorrowKind,
pub new_line: u32,
}
impl BorrowConflict {
pub fn new(
variable: VarId,
existing: ActiveBorrowV2,
new_kind: BorrowKind,
new_line: u32,
) -> Self {
Self {
variable,
existing,
new_kind,
new_line,
}
}
}
impl fmt::Display for BorrowConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let existing_kind = match self.existing.kind {
BorrowKind::Shared => "shared",
BorrowKind::Mutable => "mutable",
};
let new_kind = match self.new_kind {
BorrowKind::Shared => "shared",
BorrowKind::Mutable => "mutable",
};
if self.existing.kind == BorrowKind::Mutable && self.new_kind == BorrowKind::Mutable {
write!(
f,
"cannot borrow as mutable more than once: \
first borrow at line {}, second borrow at line {}",
self.existing.start_line, self.new_line
)
} else {
write!(
f,
"cannot borrow as {} because already borrowed as {}: \
existing borrow at line {}, new borrow at line {}",
new_kind, existing_kind, self.existing.start_line, self.new_line
)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MoveError {
pub variable: VarId,
pub moved_at: u32,
pub used_at: u32,
}
impl fmt::Display for MoveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"use of moved value: moved at line {}, used at line {}",
self.moved_at, self.used_at
)
}
}
#[derive(Debug, Clone)]
pub struct BorrowAnalysis {
pub conflicts: Vec<BorrowConflict>,
pub move_errors: Vec<MoveError>,
}
impl BorrowAnalysis {
pub fn has_issues(&self) -> bool {
!self.conflicts.is_empty() || !self.move_errors.is_empty()
}
pub fn issue_count(&self) -> usize {
self.conflicts.len() + self.move_errors.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct BorrowTrackerV2 {
borrows: HashMap<VarId, SmallVec<[ActiveBorrowV2; 2]>>,
}
impl BorrowTrackerV2 {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
borrows: HashMap::with_capacity(capacity),
}
}
pub fn add_borrow(
&mut self,
source: VarId,
borrower: VarId,
kind: BorrowKind,
line: u32,
) -> Vec<BorrowConflict> {
let conflicts = self.conflicts(source, kind, line);
self.borrows
.entry(source)
.or_default()
.push(ActiveBorrowV2::new(borrower, kind, line));
conflicts
}
pub fn end_borrow(&mut self, borrower: VarId, line: u32) {
for borrows in self.borrows.values_mut() {
for borrow in borrows.iter_mut() {
if borrow.borrower == borrower && borrow.end_line.is_none() {
borrow.end_at(line);
}
}
}
}
pub fn conflicts(&self, source: VarId, kind: BorrowKind, at_line: u32) -> Vec<BorrowConflict> {
self.borrows
.get(&source)
.map(|borrows| {
borrows
.iter()
.filter(|b| b.conflicts_with(kind, at_line))
.map(|b| BorrowConflict::new(source, b.clone(), kind, at_line))
.collect()
})
.unwrap_or_default()
}
pub fn active_borrows_at(&self, source: VarId, line: u32) -> Vec<&ActiveBorrowV2> {
self.borrows
.get(&source)
.map(|borrows| borrows.iter().filter(|b| b.is_active_at(line)).collect())
.unwrap_or_default()
}
pub fn active_borrows(&self, source: VarId) -> &[ActiveBorrowV2] {
self.borrows
.get(&source)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn clear(&mut self) {
self.borrows.clear();
}
pub fn has_active_borrows(&self, source: VarId, at_line: u32) -> bool {
self.borrows
.get(&source)
.map(|borrows| borrows.iter().any(|b| b.is_active_at(at_line)))
.unwrap_or(false)
}
pub fn has_active_mut_borrow(&self, source: VarId, at_line: u32) -> bool {
self.borrows
.get(&source)
.map(|borrows| {
borrows
.iter()
.any(|b| b.is_active_at(at_line) && b.kind == BorrowKind::Mutable)
})
.unwrap_or(false)
}
pub fn tracked_var_count(&self) -> usize {
self.borrows.len()
}
pub fn total_borrow_count(&self) -> usize {
self.borrows.values().map(|v| v.len()).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol::SymbolId;
use slotmap::SlotMap;
struct TestVars {
symbols: SlotMap<SymbolId, &'static str>,
mapping: super::super::var_id::VarSymbolMapping,
}
impl TestVars {
fn new() -> Self {
Self {
symbols: SlotMap::with_key(),
mapping: super::super::var_id::VarSymbolMapping::new(),
}
}
fn var(&mut self, name: &'static str) -> VarId {
let sym = self.symbols.insert(name);
self.mapping.register(sym)
}
}
#[test]
fn test_borrow_state_can_read() {
let mut vars = TestVars::new();
let v = vars.var("v");
assert!(BorrowStateV2::Owned.can_read());
assert!(BorrowStateV2::SharedRef {
source: v,
start_line: 1
}
.can_read());
assert!(BorrowStateV2::MutRef {
source: v,
start_line: 1
}
.can_read());
assert!(!BorrowStateV2::Moved { to: v, at_line: 1 }.can_read());
assert!(!BorrowStateV2::Dropped { at_line: 1 }.can_read());
}
#[test]
fn test_borrow_state_can_mutate() {
let mut vars = TestVars::new();
let v = vars.var("v");
assert!(BorrowStateV2::Owned.can_mutate());
assert!(BorrowStateV2::MutRef {
source: v,
start_line: 1
}
.can_mutate());
assert!(!BorrowStateV2::SharedRef {
source: v,
start_line: 1
}
.can_mutate());
assert!(!BorrowStateV2::Moved { to: v, at_line: 1 }.can_mutate());
}
#[test]
fn test_active_borrow_is_active_at() {
let mut vars = TestVars::new();
let borrower = vars.var("borrower");
let mut borrow = ActiveBorrowV2::new(borrower, BorrowKind::Shared, 10);
assert!(borrow.is_active_at(10));
assert!(borrow.is_active_at(15));
assert!(borrow.is_active_at(100));
assert!(!borrow.is_active_at(5));
borrow.end_at(20);
assert!(borrow.is_active_at(10));
assert!(borrow.is_active_at(15));
assert!(!borrow.is_active_at(20));
assert!(!borrow.is_active_at(25));
}
#[test]
fn test_borrow_tracker_no_conflict_shared() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let source = vars.var("source");
let b1 = vars.var("b1");
let b2 = vars.var("b2");
let conflicts = tracker.add_borrow(source, b1, BorrowKind::Shared, 10);
assert!(conflicts.is_empty());
let conflicts = tracker.add_borrow(source, b2, BorrowKind::Shared, 15);
assert!(conflicts.is_empty());
}
#[test]
fn test_borrow_tracker_conflict_mut_mut() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let source = vars.var("source");
let b1 = vars.var("b1");
let b2 = vars.var("b2");
let conflicts = tracker.add_borrow(source, b1, BorrowKind::Mutable, 10);
assert!(conflicts.is_empty());
let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 15);
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].existing.borrower, b1);
assert_eq!(conflicts[0].new_kind, BorrowKind::Mutable);
}
#[test]
fn test_borrow_tracker_conflict_shared_then_mut() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let source = vars.var("source");
let b1 = vars.var("b1");
let b2 = vars.var("b2");
let conflicts = tracker.add_borrow(source, b1, BorrowKind::Shared, 10);
assert!(conflicts.is_empty());
let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 15);
assert_eq!(conflicts.len(), 1);
}
#[test]
fn test_borrow_tracker_end_borrow() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let source = vars.var("source");
let borrower = vars.var("borrower");
let b2 = vars.var("b2");
tracker.add_borrow(source, borrower, BorrowKind::Mutable, 10);
tracker.end_borrow(borrower, 20);
let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 25);
assert!(conflicts.is_empty());
}
#[test]
fn test_borrow_tracker_has_active_borrows() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let source = vars.var("source");
let borrower = vars.var("borrower");
assert!(!tracker.has_active_borrows(source, 10));
tracker.add_borrow(source, borrower, BorrowKind::Shared, 10);
assert!(tracker.has_active_borrows(source, 15));
tracker.end_borrow(borrower, 20);
assert!(!tracker.has_active_borrows(source, 25));
}
#[test]
fn test_borrow_tracker_stats() {
let mut tracker = BorrowTrackerV2::new();
let mut vars = TestVars::new();
let s1 = vars.var("s1");
let s2 = vars.var("s2");
let b1 = vars.var("b1");
let b2 = vars.var("b2");
let b3 = vars.var("b3");
tracker.add_borrow(s1, b1, BorrowKind::Shared, 10);
tracker.add_borrow(s1, b2, BorrowKind::Shared, 15);
tracker.add_borrow(s2, b3, BorrowKind::Mutable, 20);
assert_eq!(tracker.tracked_var_count(), 2);
assert_eq!(tracker.total_borrow_count(), 3);
}
}