use rustc_hash::FxHashMap;
use super::problems::OwnershipProblems;
use crate::core::{BindingId, ReferenceKind};
use crate::diagnostic::{Diagnostic, Diagnostics};
use crate::types::{
FieldPlace, Type, TypedArgument, TypedExpr, TypedExprKind, TypedLeaf, TypedProgram,
};
struct ChangedArgument<'e> {
argument: &'e TypedExpr,
place: &'e TypedExpr,
}
pub struct AliasedChanges<'a> {
program: &'a TypedProgram,
function_params: &'a FxHashMap<BindingId, Vec<Type>>,
}
impl<'a> AliasedChanges<'a> {
pub fn new(
program: &'a TypedProgram,
function_params: &'a FxHashMap<BindingId, Vec<Type>>,
) -> Self {
Self {
program,
function_params,
}
}
pub fn check(&self) -> Diagnostics {
let mut diagnostics = Diagnostics::default();
for definition in self
.program
.modules
.iter()
.flat_map(|module| &module.definitions)
{
self.visit(&definition.value, &mut diagnostics);
}
diagnostics
}
fn visit(&self, expr: &TypedExpr, diagnostics: &mut Diagnostics) {
if let TypedExprKind::Call(callee, arguments) = &expr.kind {
self.call(callee, arguments, diagnostics);
}
for child in expr.children() {
self.visit(child, diagnostics);
}
}
fn call(&self, callee: &TypedExpr, arguments: &[TypedArgument], diagnostics: &mut Diagnostics) {
let params = self.callee_params(callee);
let changed: Vec<ChangedArgument> = arguments
.iter()
.enumerate()
.filter_map(|(position, argument)| {
Self::changed_place(argument, params.get(position)).map(|place| ChangedArgument {
argument: &argument.value,
place,
})
})
.collect();
for (later, second) in changed.iter().enumerate() {
if let Some(first) = changed[..later]
.iter()
.find(|first| Self::overlap(first.place, second.place))
{
diagnostics.push(self.aliased_change(first, second));
}
}
}
fn aliased_change(&self, first: &ChangedArgument, second: &ChangedArgument) -> Diagnostic {
let places = [first, second].map(|changed| {
changed
.place
.field_place()
.expect("a changed argument is a place")
});
let outer = places
.iter()
.min_by_key(|place| place.path.len())
.expect("two places");
OwnershipProblems::aliased_change(
&outer.written(&self.program.bindings),
first.argument.span,
second.argument.span,
)
}
fn callee_params(&self, callee: &TypedExpr) -> Vec<Type> {
let declared = match callee.kind {
TypedExprKind::Leaf(TypedLeaf::Global(function)) => self.function_params.get(&function),
_ => None,
};
match (declared, &callee.ty) {
(Some(params), _) => params.clone(),
(None, Type::Function(_, params, _)) => params.clone(),
_ => Vec::new(),
}
}
fn changed_place<'e>(
argument: &'e TypedArgument,
param: Option<&Type>,
) -> Option<&'e TypedExpr> {
let place: &TypedExpr = match (&argument.value.kind, param) {
(TypedExprKind::Borrow(ReferenceKind::Mutable, place), _) => place,
(_, Some(Type::Mut(..))) => &argument.value,
_ => return None,
};
place.is_place().then_some(place)
}
fn overlap(first: &TypedExpr, second: &TypedExpr) -> bool {
let (Some(first), Some(second)) = (first.field_place(), second.field_place()) else {
return false;
};
first.root == second.root && Self::nested(&first, &second)
}
fn nested(first: &FieldPlace, second: &FieldPlace) -> bool {
first
.path
.iter()
.zip(&second.path)
.all(|(left, right)| left.name == right.name)
}
}