Skip to content

Commit bb7c99b

Browse files
authored
Implement partial equivalence and extend forc migrate tool (#6900)
## Description This PR: - implements partial equivalence in the `core::ops`, as explained in detail in #6883. The implementation is opt-in and behind the `partial_eq` experimental feature flag. - implements `forc migrate` migration steps for automatic migration to `partial_eq` feature. - extends the infrastructure for writing `forc migrate` migrations. - preserves `Annotation`s on `LexedModule`s in the `LexedProgram`. (Those were before stripped off and not available in the compiled `Programs`. This resulted in loss off `//!` doc-comments that are represented as `doc-comment` annotations.) Extensions in the `forc migrate` infrastructure include: - possibility to postpone migration steps. This allows writing multi-step migrations where the first step is usually early adopting an experimental feature by using `cfg` attributes in code. This possibility is crucial for migrating our own codebase where we want to early-adopt and test and stabilize experimental features. - possibility to match elements on both mutable and immutable parsed trees. The initial assumption that immutable matching on typed trees will always be sufficient does not hold. Experimental `cfg` attributes can remove parts of typed trees that might be needed in analysis. - `LexedLocate(As)Annotated` for easier location and retrieval of `Annotated` elements. - additional implementations of existing traits. - additional `Modifier`s for modifying existing elements in the lexed tree. - `New` struct for convenient creation of often needed lexed tree elements. Note that we do not expect migrations to generate large new parts of lexed trees, but mostly to modify or copy and modify existing ones. Almost all of the changes in the Sway files are done automatically by the migration steps. The only manual change was in the `core` library (`std` library is also automatically migrated) and in slight extension of two of the tests. Note that some of the tests have `Eq` traits in trait constraints. As explained in the #6883, it is not necessary to change those to `PartialEq`. We might consider changing some of them, for testing purposes, and such a change is done on one of the tests that was testing the behavior of the `Eq` trait. But for the majority of the tests, there is no strict need for switching from `Eq` to `PartialEq`. Rolling `PartialEq` out in the tests provoked three existing issues that will be fixed in separate PRs: #6897, #6898, #6899. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent 70c84ca commit bb7c99b

File tree

171 files changed

+6608
-1163
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

171 files changed

+6608
-1163
lines changed

.github/workflows/ci.yml

+10
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ jobs:
479479
run: cargo run --locked --release -p forc -- build --experimental error_type --release --locked --path ./test/src/sdk-harness
480480
- name: Cargo Test sway-lib-std - Experimental Feature 'error_type'
481481
run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture
482+
- name: Build All Tests - Experimental Feature 'partial_eq'
483+
run: cargo run --locked --release -p forc -- build --experimental partial_eq --release --locked --path ./test/src/sdk-harness
484+
- name: Cargo Test sway-lib-std - Experimental Feature 'partial_eq'
485+
run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture
482486

483487
forc-run-benchmarks:
484488
runs-on: buildjet-4vcpu-ubuntu-2204
@@ -547,18 +551,24 @@ jobs:
547551
run: forc build --experimental storage_domains --path sway-lib-core && forc test --experimental storage_domains --path sway-lib-core
548552
- name: Run Core Unit Tests - Experimental feature 'error_type'
549553
run: forc build --experimental error_type --path sway-lib-core && forc test --experimental error_type --path sway-lib-core
554+
- name: Run Core Unit Tests - Experimental feature 'partial_eq'
555+
run: forc build --experimental partial_eq --path sway-lib-core && forc test --experimental partial_eq --path sway-lib-core
550556
- name: Run Std Unit Tests
551557
run: forc build --path sway-lib-std && forc test --path sway-lib-std
552558
- name: Run Std Unit Tests - Experimental feature 'storage_domains'
553559
run: forc build --experimental storage_domains --path sway-lib-std && forc test --experimental storage_domains --path sway-lib-std
554560
- name: Run Std Unit Tests - Experimental feature 'error_type'
555561
run: forc build --experimental error_type --path sway-lib-std && forc test --experimental error_type --path sway-lib-std
562+
- name: Run Std Unit Tests - Experimental feature 'partial_eq'
563+
run: forc build --experimental partial_eq --path sway-lib-std && forc test --experimental partial_eq --path sway-lib-std
556564
- name: Run In Language Unit Tests
557565
run: forc build --path test/src/in_language_tests && forc test --path test/src/in_language_tests
558566
- name: Run In Language Unit Tests - Experimental feature 'storage_domains'
559567
run: forc build --experimental storage_domains --path test/src/in_language_tests && forc test --experimental storage_domains --path test/src/in_language_tests
560568
- name: Run In Language Unit Tests - Experimental feature 'error_type'
561569
run: forc build --experimental error_type --path test/src/in_language_tests && forc test --experimental error_type --path test/src/in_language_tests
570+
- name: Run In Language Unit Tests - Experimental feature 'partial_eq'
571+
run: forc build --experimental partial_eq --path test/src/in_language_tests && forc test --experimental partial_eq --path test/src/in_language_tests
562572

563573
forc-pkg-fuels-deps-check:
564574
runs-on: buildjet-4vcpu-ubuntu-2204

Cargo.lock

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ devault = "0.2"
135135
dialoguer = "0.11"
136136
dirs = "5.0"
137137
downcast-rs = "1.2"
138+
duplicate = "2.0"
138139
either = "1.9"
139140
ethabi = { package = "fuel-ethabi", version = "18.0" }
140141
etk-asm = { package = "fuel-etk-asm", version = "0.3.1-dev" }

forc-plugins/forc-migrate/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ repository.workspace = true
1111
[dependencies]
1212
anyhow.workspace = true
1313
clap = { workspace = true, features = ["derive"] }
14+
duplicate.workspace = true
1415
forc-pkg.workspace = true
1516
forc-tracing.workspace = true
1617
forc-util.workspace = true

forc-plugins/forc-migrate/src/cli/commands/check.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ pub(crate) fn exec(command: Command) -> Result<()> {
4444
for migration_step in migration_steps.iter() {
4545
let migration_point_spans = match migration_step.kind {
4646
MigrationStepKind::Instruction(instruction) => instruction(&program_info)?,
47-
MigrationStepKind::CodeModification(modification, _) => {
47+
MigrationStepKind::CodeModification(modification, ..) => {
4848
modification(&mut program_info.as_mut(), DryRun::Yes)?
4949
}
50-
MigrationStepKind::Interaction(instruction, _, _) => instruction(&program_info)?,
50+
MigrationStepKind::Interaction(instruction, ..) => instruction(&program_info)?,
5151
};
5252

5353
check_result.push((feature, migration_step, migration_point_spans));

forc-plugins/forc-migrate/src/cli/commands/run.rs

+81-36
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use clap::Parser;
88
use forc_tracing::{println_action_green, println_action_yellow, println_yellow_bold};
99
use forc_util::{format_diagnostic, fs_locking::is_file_dirty};
1010
use itertools::Itertools;
11-
use sway_ast::{attribute::Annotated, Module};
11+
use sway_ast::Module;
1212
use sway_core::{
1313
language::lexed::{LexedModule, LexedProgram},
1414
Engines,
@@ -27,7 +27,10 @@ use crate::{
2727
},
2828
},
2929
get_migration_steps_or_return, instructive_error,
30-
migrations::{DryRun, MigrationStep, MigrationStepKind, MigrationSteps, ProgramInfo},
30+
migrations::{
31+
ContinueMigrationProcess, DryRun, InteractionResponse, MigrationStep, MigrationStepKind,
32+
MigrationSteps, ProgramInfo,
33+
},
3134
};
3235

3336
forc_util::cli_examples! {
@@ -127,6 +130,7 @@ pub(crate) fn exec(command: Command) -> Result<()> {
127130
)
128131
.0;
129132
let mut current_feature_migration_has_code_changes = false;
133+
let mut num_of_postponed_steps = 0;
130134
for (feature, migration_steps) in migration_steps.iter() {
131135
for migration_step in migration_steps.iter() {
132136
match migration_step.kind {
@@ -145,7 +149,11 @@ pub(crate) fn exec(command: Command) -> Result<()> {
145149
println_yellow_bold("If you've already reviewed the above points, you can ignore this info.");
146150
}
147151
}
148-
MigrationStepKind::CodeModification(modification, manual_migration_actions) => {
152+
MigrationStepKind::CodeModification(
153+
modification,
154+
manual_migration_actions,
155+
continue_migration_process,
156+
) => {
149157
let occurrences_spans = modification(&mut program_info.as_mut(), DryRun::No)?;
150158

151159
output_modified_modules(
@@ -159,7 +167,9 @@ pub(crate) fn exec(command: Command) -> Result<()> {
159167
feature,
160168
migration_step,
161169
manual_migration_actions,
170+
continue_migration_process,
162171
&occurrences_spans,
172+
InteractionResponse::None,
163173
&mut current_feature_migration_has_code_changes,
164174
);
165175
if stop_migration_process == StopMigrationProcess::Yes {
@@ -170,6 +180,7 @@ pub(crate) fn exec(command: Command) -> Result<()> {
170180
instruction,
171181
interaction,
172182
manual_migration_actions,
183+
continue_migration_process,
173184
) => {
174185
let instruction_occurrences_spans = instruction(&program_info)?;
175186

@@ -183,9 +194,13 @@ pub(crate) fn exec(command: Command) -> Result<()> {
183194

184195
// We have occurrences, let's continue with the interaction.
185196
if !instruction_occurrences_spans.is_empty() {
186-
let interaction_occurrences_spans =
197+
let (interaction_response, interaction_occurrences_spans) =
187198
interaction(&mut program_info.as_mut())?;
188199

200+
if interaction_response == InteractionResponse::PostponeStep {
201+
num_of_postponed_steps += 1;
202+
}
203+
189204
output_modified_modules(
190205
&build_instructions.manifest_dir()?,
191206
&program_info,
@@ -197,7 +212,9 @@ pub(crate) fn exec(command: Command) -> Result<()> {
197212
feature,
198213
migration_step,
199214
manual_migration_actions,
215+
continue_migration_process,
200216
&interaction_occurrences_spans,
217+
interaction_response,
201218
&mut current_feature_migration_has_code_changes,
202219
);
203220
if stop_migration_process == StopMigrationProcess::Yes {
@@ -212,7 +229,7 @@ pub(crate) fn exec(command: Command) -> Result<()> {
212229
// stop for a review before continuing with the next feature.
213230
if current_feature_migration_has_code_changes {
214231
if *feature == last_migration_feature {
215-
print_migration_finished_action();
232+
print_migration_finished_action(num_of_postponed_steps);
216233
} else {
217234
print_continue_migration_action("Review the changed code");
218235
}
@@ -224,7 +241,7 @@ pub(crate) fn exec(command: Command) -> Result<()> {
224241
// We've run through all the migration steps.
225242
// Print the confirmation message, even if there were maybe infos
226243
// displayed for manual reviews.
227-
print_migration_finished_action();
244+
print_migration_finished_action(num_of_postponed_steps);
228245

229246
Ok(())
230247
}
@@ -235,16 +252,23 @@ enum StopMigrationProcess {
235252
No,
236253
}
237254

255+
#[allow(clippy::too_many_arguments)]
238256
fn print_modification_result(
239257
max_len: usize,
240258
feature: &Feature,
241259
migration_step: &MigrationStep,
242260
manual_migration_actions: &[&str],
261+
continue_migration_process: ContinueMigrationProcess,
243262
occurrences_spans: &[Span],
263+
interaction_response: InteractionResponse,
244264
current_feature_migration_has_code_changes: &mut bool,
245265
) -> StopMigrationProcess {
246266
if occurrences_spans.is_empty() {
247-
print_checked_action(max_len, feature, migration_step);
267+
if interaction_response == InteractionResponse::PostponeStep {
268+
print_postponed_action(max_len, feature, migration_step);
269+
} else {
270+
print_checked_action(max_len, feature, migration_step);
271+
}
248272
StopMigrationProcess::No
249273
} else {
250274
print_changing_code_action(max_len, feature, migration_step);
@@ -256,25 +280,35 @@ fn print_modification_result(
256280
plural_s(occurrences_spans.len())
257281
);
258282

259-
// Check if we can proceed with the next migration step or break for manual action.
260-
if !migration_step.has_manual_actions() {
261-
// Mark the feature as having made code changes in the migration, and proceed with the
262-
// next migration step *within the same feature*, if any.
263-
*current_feature_migration_has_code_changes = true;
264-
265-
StopMigrationProcess::No
266-
} else {
267-
// Display the manual migration actions and stop the further execution of the migration steps.
268-
println!();
269-
println!("You still need to manually:");
270-
manual_migration_actions
271-
.iter()
272-
.for_each(|help| println!("- {help}"));
273-
println!();
274-
println!("{}", detailed_migration_guide_msg(feature));
275-
print_continue_migration_action("Do the above manual changes");
283+
// Check if we can proceed with the next migration step,
284+
// or we have a mandatory stop, or a stop for completing manual actions.
285+
match continue_migration_process {
286+
ContinueMigrationProcess::Never => {
287+
print_continue_migration_action("Review the changed code");
276288

277-
StopMigrationProcess::Yes
289+
StopMigrationProcess::Yes
290+
}
291+
ContinueMigrationProcess::IfNoManualMigrationActionsNeeded => {
292+
if !migration_step.has_manual_actions() {
293+
// Mark the feature as having made code changes in the migration, and proceed with the
294+
// next migration step *within the same feature*, if any.
295+
*current_feature_migration_has_code_changes = true;
296+
297+
StopMigrationProcess::No
298+
} else {
299+
// Display the manual migration actions and stop the further execution of the migration steps.
300+
println!();
301+
println!("You still need to manually:");
302+
manual_migration_actions
303+
.iter()
304+
.for_each(|help| println!("- {help}"));
305+
println!();
306+
println!("{}", detailed_migration_guide_msg(feature));
307+
print_continue_migration_action("Do the above manual changes");
308+
309+
StopMigrationProcess::Yes
310+
}
311+
}
278312
}
279313
}
280314
}
@@ -347,17 +381,10 @@ fn output_changed_lexed_program(
347381
modified_modules: &ModifiedModules,
348382
lexed_module: &LexedModule,
349383
) -> Result<()> {
350-
if let Some(path) = modified_modules.get_path_if_modified(&lexed_module.tree) {
384+
if let Some(path) = modified_modules.get_path_if_modified(&lexed_module.tree.value) {
351385
let mut formatter = Formatter::from_dir(manifest_dir)?;
352386

353-
let annotated_module = Annotated {
354-
// TODO: Handle annotations instead of stripping them.
355-
// See: https://github.com/FuelLabs/sway/issues/6802
356-
attribute_list: vec![],
357-
value: lexed_module.tree.clone(),
358-
};
359-
360-
let code = formatter.format_module(&annotated_module)?;
387+
let code = formatter.format_module(&lexed_module.tree.clone())?;
361388

362389
std::fs::write(path, code)?;
363390
}
@@ -411,8 +438,26 @@ fn print_review_action(max_len: usize, feature: &Feature, migration_step: &Migra
411438
);
412439
}
413440

414-
fn print_migration_finished_action() {
415-
println_action_green("Finished", PROJECT_IS_COMPATIBLE);
441+
fn print_postponed_action(max_len: usize, feature: &Feature, migration_step: &MigrationStep) {
442+
println_action_yellow(
443+
"Postponed",
444+
&full_migration_step_title(max_len, feature, migration_step),
445+
);
446+
}
447+
448+
fn print_migration_finished_action(num_of_postponed_steps: usize) {
449+
if num_of_postponed_steps > 0 {
450+
println_action_green(
451+
"Finished",
452+
&format!(
453+
"Run `forc migrate` at a later point to resolve {} postponed migration step{}",
454+
number_to_str(num_of_postponed_steps),
455+
plural_s(num_of_postponed_steps),
456+
),
457+
)
458+
} else {
459+
println_action_green("Finished", PROJECT_IS_COMPATIBLE);
460+
}
416461
}
417462

418463
fn print_continue_migration_action(txt: &str) {

forc-plugins/forc-migrate/src/cli/shared.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,16 @@ pub(crate) fn create_migration_diagnostic(
248248
})
249249
.chain(match migration_step.kind {
250250
MigrationStepKind::Instruction(_) => vec![],
251-
MigrationStepKind::CodeModification(_, []) => vec![],
252-
MigrationStepKind::CodeModification(_, manual_migration_actions) => {
251+
MigrationStepKind::CodeModification(_, [], _) => vec![],
252+
MigrationStepKind::CodeModification(_, manual_migration_actions, _) => {
253253
get_manual_migration_actions_help(manual_migration_actions)
254254
}
255-
MigrationStepKind::Interaction(_, _, []) => vec![
255+
MigrationStepKind::Interaction(_, _, [], _) => vec![
256256
"This migration step will interactively modify the code, based on your input."
257257
.to_string(),
258258
Diagnostic::help_empty_line(),
259259
],
260-
MigrationStepKind::Interaction(_, _, manual_migration_actions) => vec![
260+
MigrationStepKind::Interaction(_, _, manual_migration_actions, _) => vec![
261261
"This migration step will interactively modify the code, based on your input."
262262
.to_string(),
263263
Diagnostic::help_empty_line(),

0 commit comments

Comments
 (0)