Skip to main content

cargo/diagnostics/rules/
blanket_hint_mostly_unused.rs

1use 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        // The primary group should always be first
174        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}