use super::liveness::LivenessResult;
use super::types::*;
use shape_ast::ast::Span;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ReturnReferenceSummary {
pub param_index: usize,
pub kind: BorrowKind,
pub projection: Option<Vec<ProjectionStep>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ReferenceOrigin {
pub root: ReferenceOriginRoot,
pub projection: Vec<ProjectionStep>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ReferenceOriginRoot {
Param(usize),
Local(SlotId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LoanSinkKind {
ReturnSlot,
ClosureEnv,
ClosureEnvMut,
ArrayStore,
ObjectStore,
EnumStore,
ArrayAssignment,
ObjectAssignment,
StructuredTaskBoundary,
DetachedTaskBoundary,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LoanSink {
pub loan_id: u32,
pub kind: LoanSinkKind,
pub sink_slot: Option<SlotId>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct BorrowAnalysis {
pub liveness: LivenessResult,
pub loans_at_point: HashMap<Point, Vec<LoanId>>,
pub loans: HashMap<LoanId, LoanInfo>,
pub errors: Vec<BorrowError>,
pub ownership_decisions: HashMap<Point, OwnershipDecision>,
pub mutability_errors: Vec<MutabilityError>,
pub return_reference_summary: Option<ReturnReferenceSummary>,
}
#[derive(Debug, Clone)]
pub struct LoanInfo {
pub id: LoanId,
pub borrowed_place: Place,
pub kind: BorrowKind,
pub issued_at: Point,
pub span: Span,
pub region_depth: u32,
}
#[derive(Debug, Clone)]
pub struct BorrowError {
pub kind: BorrowErrorKind,
pub span: Span,
pub conflicting_loan: LoanId,
pub loan_span: Span,
pub last_use_span: Option<Span>,
pub repairs: Vec<RepairCandidate>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BorrowErrorKind {
ConflictSharedExclusive,
ConflictExclusiveExclusive,
ReadWhileExclusivelyBorrowed,
WriteWhileBorrowed,
ReferenceEscape,
ReferenceStoredInArray,
ReferenceStoredInObject,
ReferenceStoredInEnum,
ReferenceEscapeIntoClosure,
UseAfterMove,
ExclusiveRefAcrossTaskBoundary,
SharedRefAcrossDetachedTask,
InconsistentReferenceReturn,
CallSiteAliasConflict,
NonSendableAcrossTaskBoundary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BorrowErrorCode {
B0001,
B0002,
B0003,
B0004,
B0005,
B0006,
B0007,
B0012,
B0013,
B0014,
}
impl BorrowErrorCode {
pub fn as_str(self) -> &'static str {
match self {
BorrowErrorCode::B0001 => "B0001",
BorrowErrorCode::B0002 => "B0002",
BorrowErrorCode::B0003 => "B0003",
BorrowErrorCode::B0004 => "B0004",
BorrowErrorCode::B0005 => "B0005",
BorrowErrorCode::B0006 => "B0006",
BorrowErrorCode::B0007 => "B0007",
BorrowErrorCode::B0012 => "B0012",
BorrowErrorCode::B0013 => "B0013",
BorrowErrorCode::B0014 => "B0014",
}
}
}
impl std::fmt::Display for BorrowErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl BorrowErrorKind {
pub fn code(&self) -> BorrowErrorCode {
match self {
BorrowErrorKind::ConflictSharedExclusive
| BorrowErrorKind::ConflictExclusiveExclusive
| BorrowErrorKind::ReadWhileExclusivelyBorrowed => BorrowErrorCode::B0001,
BorrowErrorKind::WriteWhileBorrowed => BorrowErrorCode::B0002,
BorrowErrorKind::ReferenceEscape
| BorrowErrorKind::ReferenceEscapeIntoClosure => BorrowErrorCode::B0003,
BorrowErrorKind::ReferenceStoredInArray
| BorrowErrorKind::ReferenceStoredInObject
| BorrowErrorKind::ReferenceStoredInEnum => BorrowErrorCode::B0004,
BorrowErrorKind::UseAfterMove => BorrowErrorCode::B0005,
BorrowErrorKind::ExclusiveRefAcrossTaskBoundary => BorrowErrorCode::B0006,
BorrowErrorKind::SharedRefAcrossDetachedTask => BorrowErrorCode::B0012,
BorrowErrorKind::InconsistentReferenceReturn => BorrowErrorCode::B0007,
BorrowErrorKind::CallSiteAliasConflict => BorrowErrorCode::B0013,
BorrowErrorKind::NonSendableAcrossTaskBoundary => BorrowErrorCode::B0014,
}
}
}
#[derive(Debug, Clone)]
pub struct RepairCandidate {
pub kind: RepairKind,
pub description: String,
pub diff: Option<RepairDiff>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RepairKind {
Reorder,
Scope,
Clone,
Downgrade,
Extract,
}
#[derive(Debug, Clone)]
pub struct RepairDiff {
pub removals: Vec<(Span, String)>,
pub additions: Vec<(Span, String)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnershipDecision {
Move,
Clone,
Copy,
}
#[derive(Debug, Clone)]
pub struct FunctionBorrowSummary {
pub param_borrows: Vec<Option<BorrowKind>>,
pub conflict_pairs: Vec<(usize, usize)>,
pub return_summary: Option<ReturnReferenceSummary>,
pub return_ownership_mode: ReturnOwnershipMode,
pub closure_param_escapes: Vec<bool>,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
)]
pub enum ReturnOwnershipMode {
NewlyOwned,
BorrowedFromParam(usize),
Shared,
Static,
Unknown,
}
impl ReturnOwnershipMode {
pub fn meet(self, other: Self) -> Self {
if self == other {
return self;
}
ReturnOwnershipMode::Unknown
}
}
impl Default for ReturnOwnershipMode {
fn default() -> Self {
ReturnOwnershipMode::Unknown
}
}
#[derive(Debug, Clone)]
pub struct MutabilityError {
pub span: Span,
pub variable_name: String,
pub declaration_span: Span,
pub is_explicit_let: bool,
pub is_const: bool,
}
impl BorrowAnalysis {
pub fn empty() -> Self {
BorrowAnalysis {
liveness: LivenessResult {
live_in: HashMap::new(),
live_out: HashMap::new(),
},
loans_at_point: HashMap::new(),
loans: HashMap::new(),
errors: Vec::new(),
ownership_decisions: HashMap::new(),
mutability_errors: Vec::new(),
return_reference_summary: None,
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty() || !self.mutability_errors.is_empty()
}
pub fn ownership_at(&self, point: Point) -> OwnershipDecision {
self.ownership_decisions
.get(&point)
.copied()
.unwrap_or(OwnershipDecision::Copy)
}
pub fn active_loans_at(&self, point: Point) -> &[LoanId] {
self.loans_at_point
.get(&point)
.map_or(&[], |v| v.as_slice())
}
pub fn loan(&self, id: LoanId) -> Option<&LoanInfo> {
self.loans.get(&id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_analysis() {
let analysis = BorrowAnalysis::empty();
assert!(!analysis.has_errors());
assert_eq!(analysis.ownership_at(Point(0)), OwnershipDecision::Copy);
assert!(analysis.active_loans_at(Point(0)).is_empty());
}
#[test]
fn test_conflict_shared_exclusive_maps_to_b0001() {
assert_eq!(
BorrowErrorKind::ConflictSharedExclusive.code(),
BorrowErrorCode::B0001
);
}
#[test]
fn test_conflict_exclusive_exclusive_maps_to_b0001() {
assert_eq!(
BorrowErrorKind::ConflictExclusiveExclusive.code(),
BorrowErrorCode::B0001
);
}
#[test]
fn test_read_while_exclusively_borrowed_maps_to_b0001() {
assert_eq!(
BorrowErrorKind::ReadWhileExclusivelyBorrowed.code(),
BorrowErrorCode::B0001
);
}
#[test]
fn test_write_while_borrowed_maps_to_b0002() {
assert_eq!(
BorrowErrorKind::WriteWhileBorrowed.code(),
BorrowErrorCode::B0002
);
}
#[test]
fn test_reference_escape_maps_to_b0003() {
assert_eq!(
BorrowErrorKind::ReferenceEscape.code(),
BorrowErrorCode::B0003
);
}
#[test]
fn test_reference_escape_into_closure_maps_to_b0003() {
assert_eq!(
BorrowErrorKind::ReferenceEscapeIntoClosure.code(),
BorrowErrorCode::B0003
);
}
#[test]
fn test_reference_stored_in_array_maps_to_b0004() {
assert_eq!(
BorrowErrorKind::ReferenceStoredInArray.code(),
BorrowErrorCode::B0004
);
}
#[test]
fn test_reference_stored_in_object_maps_to_b0004() {
assert_eq!(
BorrowErrorKind::ReferenceStoredInObject.code(),
BorrowErrorCode::B0004
);
}
#[test]
fn test_reference_stored_in_enum_maps_to_b0004() {
assert_eq!(
BorrowErrorKind::ReferenceStoredInEnum.code(),
BorrowErrorCode::B0004
);
}
#[test]
fn test_use_after_move_maps_to_b0005() {
assert_eq!(
BorrowErrorKind::UseAfterMove.code(),
BorrowErrorCode::B0005
);
}
#[test]
fn test_exclusive_ref_across_task_boundary_maps_to_b0006() {
assert_eq!(
BorrowErrorKind::ExclusiveRefAcrossTaskBoundary.code(),
BorrowErrorCode::B0006
);
}
#[test]
fn test_inconsistent_reference_return_maps_to_b0007() {
assert_eq!(
BorrowErrorKind::InconsistentReferenceReturn.code(),
BorrowErrorCode::B0007
);
}
#[test]
fn test_borrow_error_code_as_str() {
assert_eq!(BorrowErrorCode::B0001.as_str(), "B0001");
assert_eq!(BorrowErrorCode::B0002.as_str(), "B0002");
assert_eq!(BorrowErrorCode::B0003.as_str(), "B0003");
assert_eq!(BorrowErrorCode::B0004.as_str(), "B0004");
assert_eq!(BorrowErrorCode::B0005.as_str(), "B0005");
assert_eq!(BorrowErrorCode::B0006.as_str(), "B0006");
assert_eq!(BorrowErrorCode::B0007.as_str(), "B0007");
}
#[test]
fn test_borrow_error_code_display() {
assert_eq!(format!("{}", BorrowErrorCode::B0001), "B0001");
assert_eq!(format!("{}", BorrowErrorCode::B0007), "B0007");
}
#[test]
fn test_all_error_kinds_have_codes() {
let all_kinds = vec![
BorrowErrorKind::ConflictSharedExclusive,
BorrowErrorKind::ConflictExclusiveExclusive,
BorrowErrorKind::ReadWhileExclusivelyBorrowed,
BorrowErrorKind::WriteWhileBorrowed,
BorrowErrorKind::ReferenceEscape,
BorrowErrorKind::ReferenceStoredInArray,
BorrowErrorKind::ReferenceStoredInObject,
BorrowErrorKind::ReferenceStoredInEnum,
BorrowErrorKind::ReferenceEscapeIntoClosure,
BorrowErrorKind::UseAfterMove,
BorrowErrorKind::ExclusiveRefAcrossTaskBoundary,
BorrowErrorKind::SharedRefAcrossDetachedTask,
BorrowErrorKind::InconsistentReferenceReturn,
];
for kind in all_kinds {
let _code = kind.code();
}
}
}