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