cargo/diagnostics/rules/
blanket_hint_mostly_unused.rs1use std::path::Path;
2
3use cargo_util_schemas::manifest::ProfilePackageSpec;
4use cargo_util_terminal::report::AnnotationKind;
5use cargo_util_terminal::report::Group;
6use cargo_util_terminal::report::Level;
7use cargo_util_terminal::report::Origin;
8use cargo_util_terminal::report::Patch;
9use cargo_util_terminal::report::Snippet;
10use tracing::{instrument, trace};
11
12use super::SUSPICIOUS;
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::diagnostics::Lint;
16use crate::diagnostics::LintLevelProduct;
17use crate::diagnostics::ScopedDiagnosticStats;
18use crate::diagnostics::get_key_value_span;
19use crate::diagnostics::workspace_rel_path;
20use crate::workspace::MaybePackage;
21use crate::workspace::Workspace;
22
23pub static LINT: &Lint = &Lint {
24 name: "blanket_hint_mostly_unused",
25 primary_group: &SUSPICIOUS,
26 msrv: Some(super::CARGO_LINTS_MSRV),
27 feature_gate: None,
28 docs: Some(
29 r#"
30### What it does
31Checks if `hint-mostly-unused` being applied to all dependencies.
32
33### Why is this bad?
34`hint-mostly-unused` indicates that most of a crate's API surface will go
35unused by anything depending on it; this hint can speed up the build by
36attempting to minimize compilation time for items that aren't used at all.
37Misapplication to crates that don't fit that criteria will slow down the build
38rather than speeding it up. It should be selectively applied to dependencies
39that meet these criteria. Applying it globally is always a misapplication and
40will likely slow down the build.
41
42### Example
43```toml
44[profile.dev.package."*"]
45hint-mostly-unused = true
46```
47
48Should instead be:
49```toml
50[profile.dev.package.huge-mostly-unused-dependency]
51hint-mostly-unused = true
52```
53"#,
54 ),
55};
56
57#[instrument(skip_all)]
58pub(crate) fn lint_workspace(
59 ws: &Workspace<'_>,
60 maybe_pkg: &MaybePackage,
61 path: &Path,
62 level: LintLevelProduct,
63 pkg_stats: &mut ScopedDiagnosticStats<'_>,
64 gctx: &GlobalContext,
65) -> CargoResult<()> {
66 if !gctx.cli_unstable().profile_hint_mostly_unused {
67 trace!("ignoring `blanket_hint_mostly_unused` without `-Zprofile-hint-mostly-unused`");
68 return Ok(());
69 }
70
71 let LintLevelProduct {
72 level: lint_level,
73 source,
74 } = level;
75
76 let level = lint_level.to_diagnostic_level();
77 let manifest_path = workspace_rel_path(ws, path);
78 let mut paths = Vec::new();
79
80 if let Some(profiles) = maybe_pkg.profiles() {
81 for (profile_name, top_level_profile) in &profiles.0 {
82 if let Some(true) = top_level_profile.hint_mostly_unused {
83 paths.push((
84 vec!["profile", profile_name.as_str(), "hint-mostly-unused"],
85 true,
86 ));
87 }
88
89 if let Some(build_override) = &top_level_profile.build_override
90 && let Some(true) = build_override.hint_mostly_unused
91 {
92 paths.push((
93 vec![
94 "profile",
95 profile_name.as_str(),
96 "build-override",
97 "hint-mostly-unused",
98 ],
99 false,
100 ));
101 }
102
103 if let Some(packages) = &top_level_profile.package
104 && let Some(profile) = packages.get(&ProfilePackageSpec::All)
105 && let Some(true) = profile.hint_mostly_unused
106 {
107 paths.push((
108 vec![
109 "profile",
110 profile_name.as_str(),
111 "package",
112 "*",
113 "hint-mostly-unused",
114 ],
115 false,
116 ));
117 }
118 }
119 }
120
121 for (i, (path, show_per_pkg_suggestion)) in paths.iter().enumerate() {
122 let title = "`hint-mostly-unused` is being blanket applied to all dependencies";
123 let help_txt =
124 "scope `hint-mostly-unused` to specific packages with a lot of unused object code";
125
126 let mut report = Vec::new();
127 let mut primary_group = Group::with_title(level.clone().primary_title(title));
128
129 if let Some(contents) = maybe_pkg.contents()
130 && let Some(document) = maybe_pkg.document()
131 && let Some(span) = get_key_value_span(document, &path)
132 && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
133 {
134 primary_group = primary_group.element(
135 Snippet::source(contents)
136 .path(&manifest_path)
137 .annotation(
138 AnnotationKind::Primary.span(table_span.key.start..table_span.key.end),
139 )
140 .annotation(AnnotationKind::Context.span(span.key.start..span.value.end)),
141 );
142 } else {
143 primary_group = primary_group.element(Origin::path(&manifest_path))
144 }
145
146 if *show_per_pkg_suggestion {
147 let help_group = Group::with_title(Level::HELP.secondary_title(help_txt));
148
149 report.push(
150 if let Some(contents) = maybe_pkg.contents()
151 && let Some(document) = maybe_pkg.document()
152 && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
153 {
154 help_group.element(Snippet::source(contents).path(&manifest_path).patch(
155 Patch::new(
156 table_span.key.end..table_span.key.end,
157 ".package.<pkg_name>",
158 ),
159 ))
160 } else {
161 help_group.element(Origin::path(&manifest_path))
162 },
163 );
164 } else {
165 primary_group = primary_group.element(Level::HELP.message(help_txt));
166 }
167
168 if i == 0 {
169 primary_group =
170 primary_group.element(Level::NOTE.message(LINT.emitted_source(lint_level, source)));
171 }
172
173 report.insert(0, primary_group);
175
176 pkg_stats.record_lint(lint_level);
177 gctx.shell().print_report(&report, lint_level.force())?;
178 }
179
180 Ok(())
181}