Skip to content

Commit 4f29e25

Browse files
committed
feat: Add lint for global use of hint-mostly-unused
1 parent d826a65 commit 4f29e25

File tree

4 files changed

+234
-7
lines changed

4 files changed

+234
-7
lines changed

src/cargo/core/workspace.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use crate::util::context::FeatureUnification;
2525
use crate::util::edit_distance;
2626
use crate::util::errors::{CargoResult, ManifestError};
2727
use crate::util::interning::InternedString;
28-
use crate::util::lints::{analyze_cargo_lints_table, check_im_a_teapot};
28+
use crate::util::lints::{
29+
analyze_cargo_lints_table, blanket_hint_mostly_unused, check_im_a_teapot,
30+
};
2931
use crate::util::toml::{InheritableFields, read_manifest};
3032
use crate::util::{
3133
Filesystem, GlobalContext, IntoUrl, context::CargoResolverConfig, context::ConfigRelativePath,
@@ -1287,9 +1289,9 @@ impl<'gctx> Workspace<'gctx> {
12871289
}
12881290

12891291
pub fn emit_ws_lints(&self) -> CargoResult<()> {
1290-
let error_count = 0;
1292+
let mut error_count = 0;
12911293

1292-
let _cargo_lints = match self.root_maybe() {
1294+
let cargo_lints = match self.root_maybe() {
12931295
MaybePackage::Package(pkg) => {
12941296
let toml = pkg.manifest().normalized_toml();
12951297
if let Some(ws) = &toml.workspace {
@@ -1314,6 +1316,19 @@ impl<'gctx> Workspace<'gctx> {
13141316
// Calls to lint functions go in here
13151317
}
13161318

1319+
// This is a short term hack to allow `blanket_hint_mostly_unused`
1320+
// to run without requiring `-Zcargo-lints`, which should hopefully
1321+
// improve the testing expierience while we are collecting feedback
1322+
if self.gctx.cli_unstable().profile_hint_mostly_unused {
1323+
blanket_hint_mostly_unused(
1324+
self.root_maybe(),
1325+
self.root_manifest(),
1326+
&cargo_lints,
1327+
&mut error_count,
1328+
self.gctx,
1329+
)?;
1330+
}
1331+
13171332
if error_count > 0 {
13181333
Err(crate::util::errors::AlreadyPrintedError::new(anyhow!(
13191334
"encountered {error_count} errors(s) while running lints"

src/cargo/util/lints.rs

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
use crate::core::{Edition, Feature, Features, Manifest, Package};
1+
use crate::core::{Edition, Feature, Features, Manifest, MaybePackage, Package};
22
use crate::{CargoResult, GlobalContext};
3-
use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
4-
use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints};
3+
use annotate_snippets::{AnnotationKind, Group, Level, Patch, Snippet};
4+
use cargo_util_schemas::manifest::{ProfilePackageSpec, TomlLintLevel, TomlToolLints};
55
use pathdiff::diff_paths;
66
use std::fmt::Display;
77
use std::ops::Range;
88
use std::path::Path;
99

1010
const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
11-
pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNKNOWN_LINTS];
11+
pub const LINTS: &[Lint] = &[BLANKET_HINT_MOSTLY_UNUSED, IM_A_TEAPOT, UNKNOWN_LINTS];
1212

1313
pub fn analyze_cargo_lints_table(
1414
pkg: &Package,
@@ -473,6 +473,153 @@ pub fn check_im_a_teapot(
473473
Ok(())
474474
}
475475

476+
const BLANKET_HINT_MOSTLY_UNUSED: Lint = Lint {
477+
name: "blanket_hint_mostly_unused",
478+
desc: "blanket_hint_mostly_unused lint",
479+
groups: &[],
480+
default_level: LintLevel::Warn,
481+
edition_lint_opts: None,
482+
feature_gate: None,
483+
docs: Some(
484+
r#"
485+
### What it does
486+
Checks if `hint-mostly-unused` being applied to all dependencies.
487+
488+
### Why it is bad
489+
`hint-mostly-unused` indicates that most of a crate's API surface will go
490+
unused by anything depending on it; this hint can speed up the build by
491+
attempting to minimize compilation time for items that aren't used at all.
492+
Misapplication to crates that don't fit that criteria will slow down the build
493+
rather than speeding it up. It should be selectively applied to dependencies
494+
that meet these criteria. Applying it globally is always a misapplication and
495+
will likely slow down the build.
496+
497+
### Example
498+
```toml
499+
[profile.dev.package."*"]
500+
hint-mostly-unused = true
501+
```
502+
503+
Should instead be:
504+
```toml
505+
[profile.dev.package.huge-mostly-unused-dependency]
506+
hint-mostly-unused = true
507+
```
508+
"#,
509+
),
510+
};
511+
512+
pub fn blanket_hint_mostly_unused(
513+
maybe_pkg: &MaybePackage,
514+
path: &Path,
515+
pkg_lints: &TomlToolLints,
516+
error_count: &mut usize,
517+
gctx: &GlobalContext,
518+
) -> CargoResult<()> {
519+
let (lint_level, reason) = BLANKET_HINT_MOSTLY_UNUSED.level(
520+
pkg_lints,
521+
maybe_pkg.edition(),
522+
maybe_pkg.unstable_features(),
523+
);
524+
525+
if lint_level == LintLevel::Allow {
526+
return Ok(());
527+
}
528+
529+
let level = lint_level.to_diagnostic_level();
530+
let manifest_path = rel_cwd_manifest_path(path, gctx);
531+
let mut paths = Vec::new();
532+
533+
if let Some(profiles) = maybe_pkg.profiles() {
534+
for (profile_name, top_level_profile) in &profiles.0 {
535+
if let Some(true) = top_level_profile.hint_mostly_unused {
536+
paths.push((
537+
vec!["profile", profile_name.as_str(), "hint-mostly-unused"],
538+
true,
539+
));
540+
}
541+
542+
if let Some(build_override) = &top_level_profile.build_override
543+
&& let Some(true) = build_override.hint_mostly_unused
544+
{
545+
paths.push((
546+
vec![
547+
"profile",
548+
profile_name.as_str(),
549+
"build-override",
550+
"hint-mostly-unused",
551+
],
552+
false,
553+
));
554+
}
555+
556+
if let Some(packages) = &top_level_profile.package
557+
&& let Some(profile) = packages.get(&ProfilePackageSpec::All)
558+
&& let Some(true) = profile.hint_mostly_unused
559+
{
560+
paths.push((
561+
vec![
562+
"profile",
563+
profile_name.as_str(),
564+
"package",
565+
"*",
566+
"hint-mostly-unused",
567+
],
568+
false,
569+
));
570+
}
571+
}
572+
}
573+
574+
for (i, (path, show_per_pkg_suggestion)) in paths.iter().enumerate() {
575+
if lint_level.is_error() {
576+
*error_count += 1;
577+
}
578+
let title = "`hint-mostly-unused` is being blanket applied to all dependencies";
579+
if let (Some(span), Some(table_span)) = (
580+
get_key_value_span(maybe_pkg.document(), &path),
581+
get_key_value_span(maybe_pkg.document(), &path[..path.len() - 1]),
582+
) {
583+
let mut report = Vec::new();
584+
let mut group = level.clone().primary_title(title).element(
585+
Snippet::source(maybe_pkg.contents())
586+
.path(&manifest_path)
587+
.annotation(AnnotationKind::Primary.span(span.key.start..span.value.end))
588+
.annotation(
589+
AnnotationKind::Visible.span(table_span.key.start..table_span.key.end),
590+
),
591+
);
592+
593+
if i == 0 {
594+
group =
595+
group
596+
.element(Level::NOTE.message(
597+
BLANKET_HINT_MOSTLY_UNUSED.emitted_source(lint_level, reason),
598+
));
599+
}
600+
report.push(group);
601+
602+
if *show_per_pkg_suggestion {
603+
report.push(
604+
Level::HELP
605+
.secondary_title("try enabling `hint-mostly-unused` for just one package")
606+
.element(
607+
Snippet::source(maybe_pkg.contents())
608+
.path(&manifest_path)
609+
.patch(Patch::new(
610+
table_span.key.end..table_span.key.end,
611+
".package.<pkg_name>",
612+
)),
613+
),
614+
);
615+
}
616+
gctx.shell().print_report(&report, lint_level.force())?;
617+
}
618+
}
619+
620+
Ok(())
621+
}
622+
476623
const UNKNOWN_LINTS: Lint = Lint {
477624
name: "unknown_lints",
478625
desc: "unknown lint",

src/doc/src/reference/lints.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,37 @@ Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only
55
## Warn-by-default
66

77
These lints are all set to the 'warn' level by default.
8+
- [`blanket_hint_mostly_unused`](#blanket_hint_mostly_unused)
89
- [`unknown_lints`](#unknown_lints)
910

11+
## `blanket_hint_mostly_unused`
12+
Set to `warn` by default
13+
14+
### What it does
15+
Checks if `hint-mostly-unused` being applied to all dependencies.
16+
17+
### Why it is bad
18+
`hint-mostly-unused` indicates that most of a crate's API surface will go
19+
unused by anything depending on it; this hint can speed up the build by
20+
attempting to minimize compilation time for items that aren't used at all.
21+
Misapplication to crates that don't fit that criteria will slow down the build
22+
rather than speeding it up. It should be selectively applied to dependencies
23+
that meet these criteria. Applying it globally is always a misapplication and
24+
will likely slow down the build.
25+
26+
### Example
27+
```toml
28+
[profile.dev.package."*"]
29+
hint-mostly-unused = true
30+
```
31+
32+
Should instead be:
33+
```toml
34+
[profile.dev.package.huge-mostly-unused-dependency]
35+
hint-mostly-unused = true
36+
```
37+
38+
1039
## `unknown_lints`
1140
Set to `warn` by default
1241

tests/testsuite/lints/blanket_hint_mostly_unused.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ hint-mostly-unused = true
2222
p.cargo("check -Zprofile-hint-mostly-unused -v")
2323
.masquerade_as_nightly_cargo(&["profile-hint-mostly-unused", "cargo-lints"])
2424
.with_stderr_data(str![[r#"
25+
[WARNING] `hint-mostly-unused` is being blanket applied to all dependencies
26+
--> Cargo.toml:8:1
27+
|
28+
7 | [profile.dev]
29+
8 | hint-mostly-unused = true
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
31+
|
32+
= [NOTE] `cargo::blanket_hint_mostly_unused` is set to `warn` by default
33+
[HELP] try enabling `hint-mostly-unused` for just one package
34+
|
35+
7 | [profile.dev.package.<pkg_name>]
36+
| +++++++++++++++++++
2537
[CHECKING] foo v0.0.1 ([ROOT]/foo)
2638
[RUNNING] `rustc --crate-name foo [..]`
2739
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
@@ -50,6 +62,14 @@ hint-mostly-unused = true
5062
p.cargo("check -Zprofile-hint-mostly-unused -v")
5163
.masquerade_as_nightly_cargo(&["profile-hint-mostly-unused", "cargo-lints"])
5264
.with_stderr_data(str![[r#"
65+
[WARNING] `hint-mostly-unused` is being blanket applied to all dependencies
66+
--> Cargo.toml:8:1
67+
|
68+
7 | [profile.dev.package."*"]
69+
8 | hint-mostly-unused = true
70+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
71+
|
72+
= [NOTE] `cargo::blanket_hint_mostly_unused` is set to `warn` by default
5373
[CHECKING] foo v0.0.1 ([ROOT]/foo)
5474
[RUNNING] `rustc --crate-name foo [..]`
5575
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
@@ -78,6 +98,14 @@ hint-mostly-unused = true
7898
p.cargo("check -Zprofile-hint-mostly-unused -v")
7999
.masquerade_as_nightly_cargo(&["profile-hint-mostly-unused", "cargo-lints"])
80100
.with_stderr_data(str![[r#"
101+
[WARNING] `hint-mostly-unused` is being blanket applied to all dependencies
102+
--> Cargo.toml:8:1
103+
|
104+
7 | [profile.dev.build-override]
105+
8 | hint-mostly-unused = true
106+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
107+
|
108+
= [NOTE] `cargo::blanket_hint_mostly_unused` is set to `warn` by default
81109
[CHECKING] foo v0.0.1 ([ROOT]/foo)
82110
[RUNNING] `rustc --crate-name foo [..]`
83111
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
@@ -115,6 +143,14 @@ authors = []
115143
p.cargo("check -Zprofile-hint-mostly-unused -v")
116144
.masquerade_as_nightly_cargo(&["profile-hint-mostly-unused", "cargo-lints"])
117145
.with_stderr_data(str![[r#"
146+
[WARNING] `hint-mostly-unused` is being blanket applied to all dependencies
147+
--> Cargo.toml:6:1
148+
|
149+
5 | [profile.dev.package."*"]
150+
6 | hint-mostly-unused = true
151+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
152+
|
153+
= [NOTE] `cargo::blanket_hint_mostly_unused` is set to `warn` by default
118154
[CHECKING] foo v0.0.1 ([ROOT]/foo/foo)
119155
[RUNNING] `rustc --crate-name foo [..]`
120156
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

0 commit comments

Comments
 (0)