#![allow(dead_code)]
#![allow(deprecated)]
use std::vec;
use crate::{
internal_error,
matching::{
item_impl, lexed_match, lexed_match_mut, literal, ty_match, with_name_mut,
LexedLocateAnnotatedMut, LexedLocateAsAnnotatedMut,
},
migrations::{
visit_all_modules, visit_all_modules_mut, visit_modules, InteractionResponse,
MutProgramInfo, Occurrence,
},
modifying::*,
print_single_choice_menu,
};
use anyhow::{bail, Ok, Result};
use itertools::Itertools;
use sway_ast::{ItemKind, Module};
use sway_core::{
language::{ty::TyModule, CallPath},
Engines,
};
use sway_error::formatting::{plural_s, Indent};
use sway_types::{Ident, Span, Spanned};
use super::{ContinueMigrationProcess, DryRun, MigrationStep, MigrationStepKind, ProgramInfo};
pub(super) const IMPLEMENT_EXPERIMENTAL_PARTIAL_EQ_AND_EQ_TRAITS: MigrationStep = MigrationStep {
title: "Implement experimental `PartialEq` and `Eq` traits",
duration: 1,
kind: MigrationStepKind::CodeModification(
implement_experimental_partial_eq_and_eq_traits,
&[],
ContinueMigrationProcess::Never,
),
help: &[
"Migration will implement `PartialEq` and `Eq` traits for every type",
"that implements `Eq` trait.",
" ",
"The new implementations will be marked as `#[cfg(experimental_partial_eq = true)]`.",
" ",
"The original `Eq` implementation will remain in code and be marked as",
"`#[cfg(experimental_partial_eq = false)]`.",
],
};
pub(super) const REMOVE_DEPRECATED_EQ_TRAIT_IMPLEMENTATIONS: MigrationStep = MigrationStep {
title: "Remove deprecated `Eq` trait implementations and `experimental_partial_eq` attributes",
duration: 1,
kind: MigrationStepKind::Interaction(
remove_deprecated_eq_trait_implementations_instruction,
remove_deprecated_eq_trait_implementations_interaction,
&[],
ContinueMigrationProcess::IfNoManualMigrationActionsNeeded,
),
help: &[
"Migration will:",
" - remove deprecated `Eq` implementations.",
" - remove the `#[cfg(experimental_partial_eq = true)]` attributes from the new `Eq`",
" and `PartialEq` implementations.",
" ",
"Run this migration only if you are switching fully to the `partial_eq` feature,",
"and do not plan to use the old `Eq` implementations anymore.",
],
};
const EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE: &str = "experimental_partial_eq";
fn remove_deprecated_eq_trait_implementations_instruction(
program_info: &ProgramInfo,
) -> Result<Vec<Occurrence>> {
fn remove_deprecated_eq_trait_implementations_instruction_impl(
_engines: &Engines,
module: &Module,
_ty_module: &TyModule,
_dry_run: DryRun,
) -> Result<Vec<Occurrence>> {
let mut result = vec![];
result.append(
&mut find_trait_impl(
module,
"Eq",
false,
false,
ResultSpanSource::ImplTraitDefinition,
)
.iter()
.map(|span| span.clone().into())
.collect(),
);
result.append(
&mut find_trait_impl(module, "Eq", true, true, ResultSpanSource::CfgAttribute)
.iter()
.map(|span| span.clone().into())
.collect(),
);
result.append(
&mut find_trait_impl(
module,
"PartialEq",
false,
true,
ResultSpanSource::CfgAttribute,
)
.iter()
.map(|span| span.clone().into())
.collect(),
);
Ok(result)
}
let res = visit_all_modules(
program_info,
DryRun::Yes,
remove_deprecated_eq_trait_implementations_instruction_impl,
)?;
Ok(res.into_iter().flatten().collect())
}
fn remove_deprecated_eq_trait_implementations_interaction(
program_info: &mut MutProgramInfo,
) -> Result<(InteractionResponse, Vec<Occurrence>)> {
fn calculate_number_of_code_removals(
_engines: &Engines,
module: &Module,
_ty_module: &TyModule,
_dry_run: DryRun,
) -> Result<Vec<(usize, usize, usize)>> {
let num_of_deprecated_eq_impls = find_trait_impl(
module,
"Eq",
false,
false,
ResultSpanSource::ImplTraitDefinition,
)
.len();
let num_of_cfg_attrs_on_new_eq =
find_trait_impl(module, "Eq", true, true, ResultSpanSource::CfgAttribute).len();
let num_of_cfg_attrs_on_new_partial_eq = find_trait_impl(
module,
"PartialEq",
false,
true,
ResultSpanSource::CfgAttribute,
)
.len();
Ok(vec![(
num_of_deprecated_eq_impls,
num_of_cfg_attrs_on_new_eq,
num_of_cfg_attrs_on_new_partial_eq,
)])
}
let numbers_of_code_removals_per_module = visit_modules(
program_info.engines,
&program_info.lexed_program.root,
&program_info.ty_program.root_module,
DryRun::Yes,
calculate_number_of_code_removals,
)?;
let (
num_of_deprecated_eq_impls,
num_of_cfg_attrs_on_new_eq,
num_of_cfg_attrs_on_new_partial_eq,
) = numbers_of_code_removals_per_module
.into_iter()
.flatten()
.fold((0, 0, 0), |acc, counts| {
(acc.0 + counts.0, acc.1 + counts.1, acc.2 + counts.2)
});
if num_of_deprecated_eq_impls == 0
&& num_of_cfg_attrs_on_new_eq == 0
&& num_of_cfg_attrs_on_new_partial_eq == 0
{
return Ok((InteractionResponse::None, vec![]));
}
println!("The following code will be removed:");
if num_of_deprecated_eq_impls > 0 {
println!(
"{}- {} deprecated `Eq` implementation{}.",
Indent::Single,
num_of_deprecated_eq_impls,
plural_s(num_of_deprecated_eq_impls)
);
}
if num_of_cfg_attrs_on_new_eq > 0 {
println!("{}- {} `#[cfg(experimental_partial_eq = true)]` attributes from new `Eq` implementation{}.", Indent::Single, num_of_cfg_attrs_on_new_eq, plural_s(num_of_cfg_attrs_on_new_eq));
}
if num_of_cfg_attrs_on_new_partial_eq > 0 {
println!("{}- {} `#[cfg(experimental_partial_eq = true)]` attributes from new `PartialEq` implementation{}.", Indent::Single, num_of_cfg_attrs_on_new_partial_eq, plural_s(num_of_cfg_attrs_on_new_partial_eq));
}
println!();
println!("Do you want to remove that code and switch fully to the `partial_eq` feature?");
println!();
if print_single_choice_menu(&[
"Yes, remove the code and switch fully to the `partial_eq` feature.",
"No, continue using deprecated `Eq` and the new `PartialEq` and `Eq` side-by-side.",
]) != 0
{
return Ok((InteractionResponse::PostponeStep, vec![]));
}
let mut result = vec![];
fn remove_deprecated_eq_impls(
_engines: &Engines,
module: &mut Module,
_ty_module: &TyModule,
_dry_run: DryRun,
) -> Result<Vec<Occurrence>> {
let mut occurrences_of_annotated_eq_impls_to_remove: Vec<Occurrence> = vec![];
let annotated_eq_impls =
lexed_match::impl_self_or_trait_decls_annotated(module).filter(|annotated| {
matches!(&annotated.value,
ItemKind::Impl(item_impl)
if item_impl::implements_trait("Eq")(&item_impl)
&& !item_impl.contents.inner.is_empty()
)
});
for annotated_eq_impl in annotated_eq_impls {
if lexed_match::cfg_attribute_standalone_single_arg(
&annotated_eq_impl.attributes,
EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE,
|arg| arg.as_ref().is_some_and(literal::is_bool_false),
)
.is_none()
{
continue;
};
occurrences_of_annotated_eq_impls_to_remove.push(annotated_eq_impl.span().into());
}
for annotated_eq_impl_occurrence in occurrences_of_annotated_eq_impls_to_remove.iter() {
modify(module).remove_annotated_item(&annotated_eq_impl_occurrence.span);
}
Ok(occurrences_of_annotated_eq_impls_to_remove)
}
let res = visit_all_modules_mut(program_info, DryRun::No, remove_deprecated_eq_impls)?;
result.append(&mut res.into_iter().flatten().collect());
fn remove_cfg_experimental_partial_eq_true_attributes(
_engines: &Engines,
module: &mut Module,
_ty_module: &TyModule,
_dry_run: DryRun,
) -> Result<Vec<Occurrence>> {
let mut occurrences_of_cfg_attributes_to_remove: Vec<Occurrence> = vec![];
let annotated_trait_impls = lexed_match_mut::impl_self_or_trait_decls_annotated(module)
.filter_map(|annotated|
if matches!(&annotated.value,
ItemKind::Impl(item_impl)
if item_impl::implements_trait("Eq")(&item_impl) && item_impl.contents.inner.is_empty() ||
item_impl::implements_trait("PartialEq")(&item_impl) && !item_impl.contents.inner.is_empty())
{
Some(annotated)
} else {
None
}
)
.collect_vec();
for annotated_trait_impl in annotated_trait_impls.iter() {
let Some(cfg_experimental_partial_eq_attr) =
lexed_match::cfg_attribute_standalone_single_arg(
&annotated_trait_impl.attributes,
EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE,
|arg| arg.as_ref().is_some_and(literal::is_bool_true),
)
else {
continue;
};
occurrences_of_cfg_attributes_to_remove
.push(cfg_experimental_partial_eq_attr.span().into());
}
for annotated_trait_impl in annotated_trait_impls {
for cfg_attribute_occurrence in occurrences_of_cfg_attributes_to_remove.iter() {
modify(annotated_trait_impl)
.remove_attribute_decl_containing_attribute(&cfg_attribute_occurrence.span);
}
}
Ok(occurrences_of_cfg_attributes_to_remove)
}
let res = visit_all_modules_mut(
program_info,
DryRun::No,
remove_cfg_experimental_partial_eq_true_attributes,
)?;
result.append(&mut res.into_iter().flatten().collect());
Ok((InteractionResponse::ExecuteStep, result))
}
enum ResultSpanSource {
ImplTraitDefinition,
CfgAttribute,
}
fn find_trait_impl(
module: &Module,
trait_name: &str,
is_empty_impl: bool,
cfg_experimental_partial_eq: bool,
result_span_source: ResultSpanSource,
) -> Vec<Span> {
let mut result = vec![];
let attributed_eq_trait_impls = lexed_match::impl_self_or_trait_decls_annotated(module)
.filter_map(|annotated| match &annotated.value {
ItemKind::Impl(item_impl) if item_impl::implements_trait(trait_name)(&item_impl) => {
Some((&annotated.attributes, item_impl))
}
_ => None,
});
for (attributes, eq_trait_impl) in attributed_eq_trait_impls {
if eq_trait_impl.contents.inner.is_empty() != is_empty_impl {
continue;
}
let expected_bool_literal = if cfg_experimental_partial_eq {
literal::is_bool_true
} else {
literal::is_bool_false
};
let Some(cfg_experimental_partial_eq_attr) =
lexed_match::cfg_attribute_standalone_single_arg(
attributes,
EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE,
|arg| arg.as_ref().is_some_and(expected_bool_literal),
)
else {
continue;
};
let result_span = match result_span_source {
ResultSpanSource::ImplTraitDefinition => {
Span::join(eq_trait_impl.impl_token.span(), &eq_trait_impl.ty.span())
}
ResultSpanSource::CfgAttribute => cfg_experimental_partial_eq_attr.span(),
};
result.push(result_span);
}
result
}
fn implement_experimental_partial_eq_and_eq_traits(
program_info: &mut MutProgramInfo,
dry_run: DryRun,
) -> Result<Vec<Occurrence>> {
fn implement_experimental_partial_eq_and_eq_traits_impl(
engines: &Engines,
lexed_module: &mut Module,
ty_module: &TyModule,
dry_run: DryRun,
) -> Result<Vec<Occurrence>> {
let mut result = vec![];
let std_ops_eq_call_path = CallPath::fullpath(&["std", "ops", "Eq"]);
let ty_impl_traits = ty_match::impl_self_or_trait_decls(ty_module)
.map(|decl| engines.de().get_impl_self_or_trait(decl))
.filter(|decl| decl.is_impl_trait())
.collect_vec();
for ty_impl_trait in ty_impl_traits {
let implemented_trait = engines.de().get_trait(
&ty_impl_trait
.implemented_trait_decl_id()
.expect("impl is a trait impl"),
);
if implemented_trait.call_path != std_ops_eq_call_path {
continue;
}
let Some((attributes, lexed_impl_eq_trait)) =
lexed_module.locate_annotated_mut(&ty_impl_trait)
else {
bail!(internal_error(
"Lexical trait implementation cannot be found."
));
};
if lexed_match_mut::cfg_attribute_arg(
attributes,
with_name_mut(EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE),
)
.is_some()
{
continue;
};
if lexed_impl_eq_trait.contents.inner.is_empty() {
continue;
}
result.push(
Span::join(
lexed_impl_eq_trait.impl_token.span(),
&lexed_impl_eq_trait.ty.span(),
)
.into(),
);
if dry_run == DryRun::Yes {
continue;
}
let insert_span = if attributes.is_empty() {
Span::empty_at_start(&lexed_impl_eq_trait.span())
} else {
Span::empty_at_end(&attributes.last().expect("attributes are not empty").span())
};
let cfg_attribute_decl =
New::cfg_experimental_attribute_decl(insert_span.clone(), "partial_eq", false);
attributes.push(cfg_attribute_decl);
let Some(annotated_impl_eq_trait) =
lexed_module.locate_as_annotated_mut(&ty_impl_trait)
else {
bail!(internal_error(
"Annotated lexical trait implementation cannot be found."
));
};
let mut annotated_impl_partial_eq_trait = annotated_impl_eq_trait.clone();
let Some(experimental_partial_eq_arg) = lexed_match_mut::cfg_attribute_arg(
&mut annotated_impl_partial_eq_trait.attributes,
with_name_mut(EXPERIMENTAL_PARTIAL_EQ_ATTRIBUTE),
) else {
bail!(internal_error(
"Attribute \"experimental_partial_eq\" cannot be found."
));
};
experimental_partial_eq_arg.value = Some(New::literal_bool(insert_span, true));
let mut annotated_impl_new_eq_trait = annotated_impl_partial_eq_trait.clone();
let ItemKind::Impl(impl_new_eq_trait) = &mut annotated_impl_new_eq_trait.value else {
bail!(internal_error(
"Annotated implementation of \"Eq\" trait is not an `Item::Impl`."
));
};
impl_new_eq_trait.contents.inner.clear();
let ItemKind::Impl(impl_partial_eq_trait) = &mut annotated_impl_partial_eq_trait.value
else {
bail!(internal_error(
"Annotated implementation of \"Eq\" trait is not an `Item::Impl`."
));
};
let path_type_last_ident = impl_partial_eq_trait
.trait_opt
.as_mut()
.expect("impl implements `Eq` trait")
.0
.last_segment_mut();
path_type_last_ident.name =
Ident::new_with_override("PartialEq".into(), path_type_last_ident.name.span());
let eq_trait_constraints =
lexed_match_mut::trait_constraints(impl_partial_eq_trait, with_name_mut("Eq"));
for eq_trait_constraint in eq_trait_constraints {
let path_type_last_ident = eq_trait_constraint.last_segment_mut();
path_type_last_ident.name =
Ident::new_with_override("PartialEq".into(), path_type_last_ident.name.span());
}
modify(lexed_module)
.insert_annotated_item_after(annotated_impl_new_eq_trait)
.insert_annotated_item_after(annotated_impl_partial_eq_trait);
}
Ok(result)
}
let res = visit_all_modules_mut(
program_info,
dry_run,
implement_experimental_partial_eq_and_eq_traits_impl,
)?;
Ok(res.into_iter().flatten().collect())
}