1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::env;
4use std::io::BufRead;
5use std::io::BufReader;
6use std::io::Cursor;
7use std::path::Path;
8use std::process::Command;
9use std::process::Stdio;
10
11use anyhow::Context;
12use cargo_metadata::Metadata;
13use cargo_metadata::MetadataCommand;
14use cargo_util::paths;
15use cargo_util_schemas::core::PackageIdSpec;
16use clap::Parser;
17use indexmap::{IndexMap, IndexSet};
18use rustfix::{collect_suggestions, CodeFix, Suggestion};
19use tracing::{trace, warn};
20
21use crate::util::cli::PackageSelection;
22use crate::{
23 core::{shell, sysroot::get_sysroot},
24 ops::check::{BuildUnit, CheckOutput, DiagnosticLevel, Message, MessageDiagnostic},
25 util::{
26 cli::CheckFlags, messages::gen_please_report_this_bug_text, package::format_package_id,
27 vcs::VcsOpts,
28 },
29 CargoResult,
30};
31
32#[derive(Debug, Parser)]
33pub struct FixitArgs {
34 #[arg(long)]
36 clippy: bool,
37
38 #[arg(long)]
40 broken_code: bool,
41
42 #[arg(long = "Zdangerous-parallel-fixes")]
44 dangerous_parallel_fixes: bool,
45
46 #[command(flatten)]
47 color: colorchoice_clap::Color,
48
49 #[command(flatten)]
50 vcs_opts: VcsOpts,
51
52 #[command(flatten)]
53 check_flags: CheckFlags,
54}
55
56impl FixitArgs {
57 pub fn exec(self) -> CargoResult<()> {
58 exec(self)
59 }
60
61 fn to_command(&self) -> Command {
62 let cmd = if self.clippy { "clippy" } else { "check" };
63 let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
64 let mut command = Command::new(cargo);
65 command.arg(cmd).args(self.check_flags.to_flags());
66 command
67 }
68}
69
70#[derive(Debug, Default)]
71struct File {
72 fixes: u32,
73 original_source: String,
74}
75
76type BuildUnitErrors = IndexMap<BuildUnit, IndexSet<String>>;
77type BuildUnitSuggestions =
78 IndexMap<BuildUnit, IndexMap<String, IndexSet<(Suggestion, Option<String>)>>>;
79
80#[tracing::instrument(skip_all)]
81fn exec(args: FixitArgs) -> CargoResult<()> {
82 args.color.write_global();
83
84 args.vcs_opts.valid_vcs()?;
85
86 let mut active_targets = IndexMap::new();
87 match fix(&args, &mut active_targets) {
88 Ok(()) => Ok(()),
89 Err(error) => {
90 for (file, original) in active_targets.values().flat_map(|files| files.iter()) {
91 paths::write(file, &original.original_source)?;
92 }
93 Err(error)
94 }
95 }
96}
97
98fn fix(
99 args: &FixitArgs,
100 active_targets: &mut IndexMap<BuildUnit, IndexMap<String, File>>,
101) -> CargoResult<()> {
102 let max_iterations: usize = env::var("CARGO_FIX_MAX_RETRIES")
103 .ok()
104 .and_then(|i| i.parse().ok())
105 .unwrap_or(4);
106 let mut iteration = 0;
107 let mut lint_cap = false;
108
109 let mut last_errors = IndexMap::new();
110 let mut claimed_files: HashMap<same_file::Handle, BuildUnit> = HashMap::new();
111 let mut package_metadata_cache = None;
112 let mut primary_packages_cache = None;
113 let mut package_graph_cache: Option<Option<PackageGraph>> = None;
114 let mut seen = HashSet::new();
115
116 loop {
117 trace!("iteration={iteration}");
118 trace!("active_targets={active_targets:?}");
119 let (messages, exit_code) = check(args, &mut lint_cap)?;
120
121 if messages.is_empty() && exit_code != Some(0) {
122 let mut command = args.to_command();
123 command.status()?;
124 anyhow::bail!("could not compile");
125 } else if !args.broken_code && exit_code != Some(0) {
126 let mut out = String::new();
127
128 if !active_targets.is_empty() {
129 out.push_str(
130 "failed to automatically apply fixes suggested by rustc\n\n\
131 after fixes were automatically applied the \
132 compiler reported errors within these files:\n\n",
133 );
134
135 for (
136 file,
137 File {
138 fixes: _,
139 original_source,
140 },
141 ) in active_targets.values().flat_map(|files| files.iter())
142 {
143 out.push_str(&format!(" * {file}\n"));
144 shell::note(format!("reverting `{file}` to its original state"))?;
145 paths::write(file, original_source)?;
146 }
147 active_targets.clear();
148 out.push('\n');
149
150 out.push_str(&gen_please_report_this_bug_text(args.clippy));
151
152 let mut errors = messages
153 .into_iter()
154 .filter_map(|e| match e {
155 CheckOutput::Message(m) => m.message.diagnostic.rendered,
156 _ => None,
157 })
158 .peekable();
159 if errors.peek().is_some() {
160 out.push_str("The errors reported are:\n");
161 }
162
163 for e in errors {
164 out.push_str(&format!("{}\n\n", e.trim_end()));
165 }
166
167 let (messages, _) = check(args, &mut lint_cap)?;
168 let mut errors = messages
169 .into_iter()
170 .filter_map(|e| match e {
171 CheckOutput::Message(m) => m.message.diagnostic.rendered,
172 _ => None,
173 })
174 .peekable();
175
176 if errors.peek().is_some() {
177 out.push_str("The original errors are:\n");
178 }
179
180 for e in errors {
181 out.push_str(&format!("{}\n\n", e.trim_end()));
182 }
183
184 shell::warn(out)?;
185 } else {
186 for e in messages.into_iter().filter_map(|e| match e {
187 CheckOutput::Message(m) => m.message.diagnostic.rendered,
188 _ => None,
189 }) {
190 shell::print_ansi_stderr(format!("{}\n\n", e.trim_end()).as_bytes())?;
191 }
192 }
193
194 shell::note("try using `--broken-code` to fix errors")?;
195 anyhow::bail!("could not compile");
196 }
197
198 let (mut errors, mut build_unit_map) = collect_errors(messages.into_iter(), &seen);
199 if build_unit_map.values().any(|file_map| !file_map.is_empty()) {
200 let primary_packages = if let Some(primary_packages) = &primary_packages_cache {
201 primary_packages
202 } else {
203 let metadata = package_metadata(&mut package_metadata_cache, &args.check_flags)?;
204 primary_packages_cache
205 .insert(PrimaryPackages::from_metadata(metadata, &args.check_flags)?)
206 };
207 retain_primary_fixes(primary_packages, &mut errors, &mut build_unit_map);
208 }
209
210 if iteration >= max_iterations {
211 if active_targets.is_empty() {
212 break;
213 }
214 let targets: Vec<_> = active_targets.keys().cloned().collect();
215 for target in targets {
216 if let Some(file_map) = build_unit_map.get(&target) {
217 let target_errors = errors.entry(target.clone()).or_default();
218 target_errors.extend(
219 file_map
220 .values()
221 .flatten()
222 .filter_map(|(_, diagnostic)| diagnostic.clone()),
223 );
224 }
225 finish_target(target, active_targets, &mut errors, &mut seen)?;
226 }
227 claimed_files.clear();
228 iteration = 0;
229 }
230
231 let mut finalized_targets = false;
232 if !active_targets.is_empty()
233 && active_targets
234 .keys()
235 .all(|target| build_unit_map.get(target).is_none_or(IndexMap::is_empty))
236 {
237 let targets: Vec<_> = active_targets.keys().cloned().collect();
238 for target in targets {
239 build_unit_map.shift_remove(&target);
240 finish_target(target, active_targets, &mut errors, &mut seen)?;
241 }
242 debug_assert!(active_targets.is_empty());
243 claimed_files.clear();
244 iteration = 0;
245 finalized_targets = true;
246 }
247
248 let mut made_changes = false;
249 let continuing_batch = !active_targets.is_empty();
252
253 for (build_unit, file_map) in build_unit_map {
254 if seen.contains(&build_unit) {
255 continue;
256 }
257
258 let build_unit_errors = errors
259 .entry(build_unit.clone())
260 .or_insert_with(IndexSet::new);
261
262 if active_targets.is_empty() && file_map.is_empty() {
263 if finalized_targets && build_unit_errors.is_empty() {
264 continue;
265 }
266 if seen.iter().all(|b| b.package_id != build_unit.package_id) {
267 shell::status("Checking", format_package_id(&build_unit.package_id)?)?;
268 }
269 for e in build_unit_errors.iter() {
270 shell::print_ansi_stderr(format!("{}\n\n", e.trim_end()).as_bytes())?;
271 }
272 errors.shift_remove(&build_unit);
273
274 seen.insert(build_unit);
275 } else if !file_map.is_empty() {
276 let was_active = active_targets.contains_key(&build_unit);
277 if continuing_batch && !was_active {
278 continue;
279 }
280
281 if !args.dangerous_parallel_fixes && !was_active && !active_targets.is_empty() {
282 if active_targets
283 .keys()
284 .any(|active| active.package_id == build_unit.package_id)
285 {
286 continue;
287 }
288
289 if package_graph_cache.is_none() {
290 let metadata =
291 package_metadata(&mut package_metadata_cache, &args.check_flags)?;
292 package_graph_cache = Some(PackageGraph::load(metadata, &args.check_flags));
293 }
294 let Some(Some(graph)) = package_graph_cache.as_mut() else {
295 continue;
296 };
297
298 let mut independent = true;
299 for active in active_targets.keys() {
300 if !graph
301 .packages_are_independent(&active.package_id, &build_unit.package_id)
302 {
303 independent = false;
304 break;
305 }
306 }
307 if !independent {
308 continue;
309 }
310 }
311
312 let handles = file_map
313 .keys()
314 .map(same_file::Handle::from_path)
315 .collect::<Result<Vec<_>, _>>()
316 .ok();
317 let serialize_target = handles.is_none();
318 if serialize_target && !was_active && !active_targets.is_empty() {
319 continue;
320 }
321 if handles.as_ref().is_some_and(|handles| {
322 handles.iter().any(|handle| {
323 claimed_files
324 .get(handle)
325 .is_some_and(|owner| owner != &build_unit)
326 })
327 }) {
328 continue;
329 }
330
331 let target_files = active_targets.entry(build_unit.clone()).or_default();
332 let changed = fix_errors(target_files, file_map, build_unit_errors)?;
333 if !changed && !was_active {
334 active_targets.shift_remove(&build_unit);
335 }
336 if changed {
337 if let Some(handles) = handles {
338 for handle in handles {
339 claimed_files.entry(handle).or_insert(build_unit.clone());
340 }
341 }
342 made_changes = true;
343 if serialize_target {
344 break;
345 }
346 }
347 }
348 }
349
350 trace!("made_changes={made_changes:?}");
351 trace!("active_targets={active_targets:?}");
352
353 last_errors = errors;
354 iteration += 1;
355
356 if !made_changes {
357 if active_targets.is_empty() {
358 break;
359 }
360 let targets: Vec<_> = active_targets.keys().cloned().collect();
361 for target in targets {
362 finish_target(target, active_targets, &mut last_errors, &mut seen)?;
363 }
364 claimed_files.clear();
365 iteration = 0;
366 continue;
367 }
368 }
369
370 for files in active_targets.values() {
371 for (name, file) in files {
372 shell::fixed(name, file.fixes)?;
373 }
374 }
375
376 for e in last_errors.iter().flat_map(|(_, e)| e) {
377 shell::print_ansi_stderr(format!("{}\n\n", e.trim_end()).as_bytes())?;
378 }
379
380 active_targets.clear();
381 Ok(())
382}
383
384#[derive(Debug)]
386struct PrimaryPackages {
387 package_ids: HashSet<String>,
388}
389
390impl PrimaryPackages {
391 fn from_metadata(metadata: &Metadata, flags: &CheckFlags) -> CargoResult<Self> {
393 let package_ids = match flags.package_selection() {
394 PackageSelection::Default => metadata
395 .workspace_default_members
396 .iter()
397 .map(|package_id| package_id.repr.clone())
398 .collect(),
399 PackageSelection::Workspace { exclude } => {
400 let matcher = PackageSpecMatcher::new(exclude)?;
401 let mut package_ids = HashSet::new();
402 for package in metadata.workspace_packages() {
403 if !matcher.matches(package)? {
404 package_ids.insert(package.id.repr.clone());
405 }
406 }
407 package_ids
408 }
409 PackageSelection::Packages(packages) => {
410 let matcher = PackageSpecMatcher::new(packages)?;
411 let mut package_ids = HashSet::new();
412 for package in metadata.workspace_packages() {
413 if matcher.matches(package)? {
414 package_ids.insert(package.id.repr.clone());
415 }
416 }
417 package_ids
418 }
419 };
420 Ok(Self { package_ids })
421 }
422
423 fn contains(&self, package_id: &str) -> bool {
424 self.package_ids.contains(package_id)
425 }
426}
427
428#[derive(Debug)]
430struct PackageSpecMatcher {
431 specs: Vec<PackageIdSpec>,
432 patterns: Vec<glob::Pattern>,
433}
434
435impl PackageSpecMatcher {
436 fn new(raw_specs: &[String]) -> CargoResult<Self> {
437 let mut specs = Vec::new();
438 let mut patterns = Vec::new();
439
440 for raw_spec in raw_specs {
441 match PackageIdSpec::parse(raw_spec) {
442 Ok(spec) => specs.push(spec),
443 Err(_) if raw_spec.contains(&['*', '?', '[', ']'][..]) => {
444 let pattern = glob::Pattern::new(raw_spec)
445 .with_context(|| format!("failed to parse package pattern `{raw_spec}`"))?;
446 patterns.push(pattern);
447 }
448 Err(error) => {
449 return Err(error).with_context(|| {
450 format!("failed to parse package specification `{raw_spec}`")
451 });
452 }
453 }
454 }
455
456 Ok(Self { specs, patterns })
457 }
458
459 fn matches(&self, package: &cargo_metadata::Package) -> CargoResult<bool> {
460 if self
461 .patterns
462 .iter()
463 .any(|pattern| pattern.matches(package.name.as_ref()))
464 {
465 return Ok(true);
466 }
467
468 let package_id = PackageIdSpec::parse(&package.id.repr)
469 .with_context(|| format!("failed to parse package ID `{}`", package.id))?;
470 Ok(self
471 .specs
472 .iter()
473 .any(|spec| package_id_matches(spec, &package_id)))
474 }
475}
476
477fn package_id_matches(spec: &PackageIdSpec, package_id: &PackageIdSpec) -> bool {
479 spec.name() == package_id.name()
480 && spec.partial_version().is_none_or(|version| {
481 package_id
482 .version()
483 .is_some_and(|package_version| version.matches(&package_version))
484 })
485 && spec.url().is_none_or(|url| package_id.url() == Some(url))
486 && spec
487 .kind()
488 .is_none_or(|kind| package_id.kind() == Some(kind))
489}
490
491fn package_metadata<'a>(
493 cache: &'a mut Option<Metadata>,
494 flags: &CheckFlags,
495) -> CargoResult<&'a Metadata> {
496 match cache {
497 Some(metadata) => Ok(metadata),
498 cache @ None => {
499 let mut command = MetadataCommand::new();
500 command.no_deps();
501 command.other_options(flags.to_metadata_flags());
502 let metadata = command.exec().context("failed to run `cargo metadata`")?;
503 Ok(cache.insert(metadata))
504 }
505 }
506}
507
508fn retain_primary_fixes(
510 primary_packages: &PrimaryPackages,
511 errors: &mut BuildUnitErrors,
512 build_unit_map: &mut BuildUnitSuggestions,
513) {
514 for (build_unit, file_map) in build_unit_map {
515 if file_map.is_empty() || primary_packages.contains(&build_unit.package_id) {
516 continue;
517 }
518
519 let build_unit_errors = errors.entry(build_unit.clone()).or_default();
520 build_unit_errors.extend(
521 file_map
522 .values()
523 .flatten()
524 .filter_map(|(_, diagnostic)| diagnostic.clone()),
525 );
526 file_map.clear();
527 }
528}
529
530#[derive(Debug)]
532struct PackageGraph {
533 dependencies: HashMap<String, Vec<String>>,
534 reachable: HashMap<String, HashSet<String>>,
535}
536
537impl PackageGraph {
538 fn load(metadata: &Metadata, flags: &CheckFlags) -> Option<Self> {
540 if metadata
542 .packages
543 .iter()
544 .any(|package| package.manifest_path.file_name() != Some("Cargo.toml"))
545 {
546 return Self::load_resolved(flags);
547 }
548
549 let package_ids_by_path: HashMap<_, _> = metadata
550 .packages
551 .iter()
552 .filter_map(|package| {
553 package
554 .manifest_path
555 .parent()
556 .map(|path| (path, package.id.repr.as_str()))
557 })
558 .collect();
559 if package_ids_by_path.len() != metadata.packages.len() {
560 return Self::load_resolved(flags);
561 }
562
563 let package_names: HashSet<_> = metadata
564 .packages
565 .iter()
566 .map(|package| package.name.as_ref())
567 .collect();
568 let mut dependencies = HashMap::with_capacity(metadata.packages.len());
569 for package in &metadata.packages {
570 let mut package_dependencies = Vec::new();
571 for dependency in &package.dependencies {
572 if let Some(path) = &dependency.path {
573 let Some(dependency_id) = package_ids_by_path.get(path.as_path()) else {
574 return Self::load_resolved(flags);
575 };
576 package_dependencies.push((*dependency_id).to_owned());
577 } else if package_names.contains(dependency.name.as_str()) {
578 return Self::load_resolved(flags);
579 }
580 }
581 dependencies.insert(package.id.repr.clone(), package_dependencies);
582 }
583
584 Some(Self {
585 dependencies,
586 reachable: HashMap::new(),
587 })
588 }
589
590 fn load_resolved(flags: &CheckFlags) -> Option<Self> {
592 let mut command = MetadataCommand::new();
593 command.other_options(flags.to_metadata_flags());
594
595 let metadata = match command.exec() {
596 Ok(metadata) => metadata,
597 Err(error) => {
598 warn!("failed to run `cargo metadata`: {error}");
599 return None;
600 }
601 };
602 let Some(resolve) = metadata.resolve else {
603 warn!("`cargo metadata` did not return a dependency graph");
604 return None;
605 };
606 let dependencies = resolve
607 .nodes
608 .into_iter()
609 .map(|node| {
610 (
611 node.id.repr,
612 node.dependencies
613 .into_iter()
614 .map(|dependency| dependency.repr)
615 .collect(),
616 )
617 })
618 .collect();
619
620 Some(Self {
621 dependencies,
622 reachable: HashMap::new(),
623 })
624 }
625
626 fn packages_are_independent(&mut self, left: &str, right: &str) -> bool {
628 left != right && !self.depends_on(left, right) && !self.depends_on(right, left)
629 }
630
631 fn depends_on(&mut self, package: &str, target: &str) -> bool {
633 if !self.reachable.contains_key(package) {
634 let Some(reachable) = self.collect_reachable(package) else {
635 return true;
636 };
637 self.reachable.insert(package.to_owned(), reachable);
638 }
639
640 self.reachable
641 .get(package)
642 .is_none_or(|reachable| reachable.contains(target))
643 }
644
645 fn collect_reachable(&self, root: &str) -> Option<HashSet<String>> {
647 let mut reachable = HashSet::new();
648 let mut pending = vec![root];
649
650 while let Some(package) = pending.pop() {
651 if !reachable.insert(package.to_owned()) {
652 continue;
653 }
654 let dependencies = self.dependencies.get(package)?;
655 pending.extend(dependencies.iter().map(String::as_str));
656 }
657
658 reachable.remove(root);
659 Some(reachable)
660 }
661}
662
663fn finish_target(
665 target: BuildUnit,
666 active_targets: &mut IndexMap<BuildUnit, IndexMap<String, File>>,
667 errors: &mut BuildUnitErrors,
668 seen: &mut HashSet<BuildUnit>,
669) -> CargoResult<()> {
670 if seen
671 .iter()
672 .all(|build_unit| build_unit.package_id != target.package_id)
673 {
674 shell::status("Checking", format_package_id(&target.package_id)?)?;
675 }
676
677 if let Some(files) = active_targets.get(&target) {
678 for (name, file) in files {
679 shell::fixed(name, file.fixes)?;
680 }
681 }
682
683 for error in errors.get(&target).into_iter().flatten() {
684 shell::print_ansi_stderr(format!("{}\n\n", error.trim_end()).as_bytes())?;
685 }
686
687 active_targets.shift_remove(&target);
688 errors.shift_remove(&target);
689 seen.insert(target);
690 Ok(())
691}
692
693fn check(args: &FixitArgs, lint_cap: &mut bool) -> CargoResult<(Vec<CheckOutput>, Option<i32>)> {
694 let mut command = args.to_command();
695 command
696 .args(["--message-format", "json-diagnostic-rendered-ansi"])
697 .stderr(Stdio::piped())
698 .stdout(Stdio::piped());
699 if *lint_cap {
700 cap_lints(&mut command);
701 }
702 let output = command.output()?;
703 let mut output = to_check_output(output);
704
705 if output.1 != Some(0) && !*lint_cap && denied_lint(&output.0) {
706 *lint_cap = true;
707 cap_lints(&mut command);
708 output = to_check_output(command.output()?);
709 }
710
711 Ok(output)
712}
713
714fn cap_lints(command: &mut Command) {
716 if let Ok(flags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
717 let separator = if flags.is_empty() { "" } else { "\u{1f}" };
718 command.env(
719 "CARGO_ENCODED_RUSTFLAGS",
720 format!("{flags}{separator}--cap-lints=warn"),
721 );
722 } else {
723 command.env(
724 "RUSTFLAGS",
725 format!(
726 "--cap-lints=warn {}",
727 env::var("RUSTFLAGS").unwrap_or("".to_owned())
728 ),
729 );
730 }
731}
732
733fn denied_lint(messages: &[CheckOutput]) -> bool {
734 messages.iter().any(|message| {
735 matches!(&message, CheckOutput::Message(message)
736 if message.message.level == DiagnosticLevel::Error
737 && message.message.diagnostic.code.is_some())
738 })
739}
740
741fn to_check_output(output: std::process::Output) -> (Vec<CheckOutput>, Option<i32>) {
742 let buf = BufReader::new(Cursor::new(output.stdout));
743 (
744 buf.lines()
745 .map_while(|l| l.ok())
746 .filter_map(|l| serde_json::from_str(&l).ok())
747 .collect(),
748 output.status.code(),
749 )
750}
751
752#[tracing::instrument(skip_all)]
753fn collect_errors(
754 messages: impl Iterator<Item = CheckOutput>,
755 seen: &HashSet<BuildUnit>,
756) -> (BuildUnitErrors, BuildUnitSuggestions) {
757 let only = HashSet::new();
758 let mut build_unit_map = IndexMap::new();
759
760 let mut errors = IndexMap::new();
761
762 for message in messages {
763 let Message {
764 build_unit,
765 message: MessageDiagnostic { diagnostic, .. },
766 } = match message {
767 CheckOutput::Message(m) => m,
768 CheckOutput::Artifact(a) => {
769 if !seen.contains(&a.build_unit) && !a.fresh {
770 build_unit_map
771 .entry(a.build_unit.clone())
772 .or_insert(IndexMap::new());
773 }
774 continue;
775 }
776 };
777
778 let errors = errors
779 .entry(build_unit.clone())
780 .or_insert_with(IndexSet::new);
781
782 if seen.contains(&build_unit) {
783 trace!("rejecting build unit `{:?}` already seen", build_unit);
784 continue;
785 }
786
787 let file_map = build_unit_map
788 .entry(build_unit.clone())
789 .or_insert(IndexMap::new());
790
791 let filter = if env::var("__CARGO_FIX_YOLO").is_ok() {
792 rustfix::Filter::Everything
793 } else {
794 rustfix::Filter::MachineApplicableOnly
795 };
796
797 let Some(suggestion) = collect_suggestions(&diagnostic, &only, filter) else {
798 trace!("rejecting as not a MachineApplicable diagnosis: {diagnostic:?}");
799 if let Some(rendered) = diagnostic.rendered {
800 errors.insert(rendered);
801 }
802 continue;
803 };
804
805 let mut file_names = suggestion
806 .solutions
807 .iter()
808 .flat_map(|s| s.replacements.iter())
809 .map(|r| &r.snippet.file_name);
810
811 let Some(file_name) = file_names.next() else {
812 trace!("rejecting as it has no solutions {:?}", suggestion);
813 if let Some(rendered) = diagnostic.rendered {
814 errors.insert(rendered);
815 }
816 continue;
817 };
818
819 if !file_names.all(|f| f == file_name) {
820 trace!("rejecting as it changes multiple files: {:?}", suggestion);
821 if let Some(rendered) = diagnostic.rendered {
822 errors.insert(rendered);
823 }
824 continue;
825 }
826
827 let file_path = Path::new(&file_name);
828 if let Ok(home) = env::var("CARGO_HOME") {
830 if file_path.starts_with(home) {
831 if let Some(rendered) = diagnostic.rendered {
832 errors.insert(rendered);
833 }
834 continue;
835 }
836 }
837
838 if file_path.is_absolute() {
839 if let Some(sysroot) = get_sysroot() {
840 if file_path.starts_with(sysroot) {
841 if let Some(rendered) = diagnostic.rendered {
842 errors.insert(rendered);
843 }
844 continue;
845 }
846 }
847 }
848
849 file_map
850 .entry(file_name.to_owned())
851 .or_insert_with(IndexSet::new)
852 .insert((suggestion, diagnostic.rendered));
853 }
854
855 (errors, build_unit_map)
856}
857
858#[tracing::instrument(skip_all)]
859fn fix_errors(
860 files: &mut IndexMap<String, File>,
861 file_map: IndexMap<String, IndexSet<(Suggestion, Option<String>)>>,
862 errors: &mut IndexSet<String>,
863) -> CargoResult<bool> {
864 let mut made_changes = false;
865 for (file, suggestions) in file_map {
866 let source = match paths::read(file.as_ref()) {
867 Ok(s) => s,
868 Err(e) => {
869 warn!("failed to read `{}`: {}", file, e);
870 errors.extend(suggestions.iter().filter_map(|(_, e)| e.clone()));
871 continue;
872 }
873 };
874
875 let mut fixed = CodeFix::new(&source);
876 let mut num_fixes = 0;
877
878 for (suggestion, rendered) in suggestions.iter().rev() {
879 match fixed.apply(suggestion) {
880 Ok(()) => num_fixes += 1,
881 Err(rustfix::Error::AlreadyReplaced {
882 is_identical: true, ..
883 }) => {}
884 Err(e) => {
885 if let Some(rendered) = rendered {
886 errors.insert(rendered.to_owned());
887 }
888 warn!("{e:?}");
889 }
890 }
891 }
892 if fixed.modified() {
893 let new_source = fixed.finish()?;
894 let file_state = files.entry(file.clone()).or_insert(File {
895 fixes: 0,
896 original_source: source,
897 });
898 paths::write(&file, new_source)?;
899 made_changes = true;
900 file_state.fixes += num_fixes;
901 }
902 }
903
904 Ok(made_changes)
905}