use rustc_hash::FxHashSet;
use crate::core::BindingId;
use crate::diagnostic::{Diagnostic, Diagnostics};
use crate::types::{
CopyRules, ReceiverKind, Type, TypedDefinition, TypedExprKind, TypedLambda, TypedProgram,
TypedTraitMethod,
};
use super::problems::OwnershipProblems;
use super::whole_value_changes::WholeValueChanges;
pub struct ChangingFunctionNames;
impl ChangingFunctionNames {
pub fn check(program: &TypedProgram) -> Diagnostics {
let impl_methods = Self::impl_methods(program);
let default_methods = Self::default_methods(program);
let mut warnings = Diagnostics::default();
for definition in program
.modules
.iter()
.flat_map(|module| &module.definitions)
{
if impl_methods.contains(&definition.binding)
|| default_methods.contains(&definition.binding)
{
continue;
}
if let Some(warning) = Self::function_warning(program, definition) {
warnings.push(warning);
}
}
for method in program
.trait_definitions
.iter()
.flat_map(|definition| &definition.methods)
{
if let Some(warning) = Self::trait_method_warning(program, method) {
warnings.push(warning);
}
}
warnings
}
pub(super) fn impl_methods(program: &TypedProgram) -> FxHashSet<BindingId> {
program
.trait_impls
.iter()
.flat_map(|implementation| implementation.methods.iter())
.map(|method| method.binding)
.collect()
}
pub(super) fn default_methods(program: &TypedProgram) -> FxHashSet<BindingId> {
program
.trait_definitions
.iter()
.flat_map(|definition| definition.methods.iter())
.filter_map(|method| method.default)
.collect()
}
fn function_warning(
program: &TypedProgram,
definition: &TypedDefinition,
) -> Option<Diagnostic> {
let TypedExprKind::Fn(lambda) = &definition.value.kind else {
return None;
};
let changes_argument = lambda
.params
.iter()
.any(|param| matches!(¶m.ty, Type::Mut(_, _)));
Self::changed_name_warning(
program,
definition.binding,
definition.span,
changes_argument,
)
.map(|warning| Self::with_replaced_parameter(warning, program, lambda))
}
fn with_replaced_parameter(
warning: Diagnostic,
program: &TypedProgram,
lambda: &TypedLambda,
) -> Diagnostic {
let replaced = lambda.params.iter().find_map(|param| match ¶m.ty {
Type::Mut(_, referent) => WholeValueChanges::site(lambda, param.binding)
.map(|site| (param.binding, referent, site)),
_ => None,
});
let Some((param, referent, site)) = replaced else {
return warning;
};
let name = program.bindings.get(param).name();
let copy = CopyRules::new(
&program.traits,
&program.generics,
&program.structs,
&program.enums,
);
if copy.is_copy(referent) {
OwnershipProblems::with_copied_parameter_change(warning, site, name)
} else {
OwnershipProblems::with_cloned_parameter_change(warning, site, name)
}
}
fn trait_method_warning(
program: &TypedProgram,
method: &TypedTraitMethod,
) -> Option<Diagnostic> {
let definition = Self::definition(program, method.binding)?;
let changes_argument =
method.receiver == Some(ReceiverKind::Mutable) || Self::takes_mut_parameter(definition);
Self::changed_name_warning(program, method.binding, definition.span, changes_argument)
}
fn definition(program: &TypedProgram, binding: BindingId) -> Option<&TypedDefinition> {
program
.modules
.iter()
.flat_map(|module| &module.definitions)
.find(|definition| definition.binding == binding)
}
fn takes_mut_parameter(definition: &TypedDefinition) -> bool {
match &definition.scheme.body {
Type::Function(_, parameters, _) => parameters
.iter()
.any(|parameter| matches!(parameter, Type::Mut(..))),
_ => false,
}
}
fn changed_name_warning(
program: &TypedProgram,
binding: BindingId,
definition_span: crate::source::Span,
changes_argument: bool,
) -> Option<Diagnostic> {
let info = program.bindings.get(binding);
let span = info.span().unwrap_or(definition_span);
(changes_argument && !info.name().ends_with('!'))
.then(|| OwnershipProblems::missing_bang(info.name(), span))
}
}