Skip to content

Commit 02320b5

Browse files
committed
Improve the diagnostics for unused generic parameters
1 parent cb4d9a1 commit 02320b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+371
-171
lines changed

compiler/rustc_error_codes/src/error_codes/E0091.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
An unnecessary type or const parameter was given in a type alias.
1+
An unnecessary type parameter was given in a type alias.
22

33
Erroneous code example:
44

55
```compile_fail,E0091
6-
type Foo<T> = u32; // error: type parameter `T` is unused
6+
type Foo<T> = u32; // error: type parameter `T` is never used
77
// or:
8-
type Foo<A,B> = Box<A>; // error: type parameter `B` is unused
8+
type Foo<A, B> = Box<A>; // error: type parameter `B` is never used
99
```
1010

1111
Please check you didn't write too many parameters. Example:

compiler/rustc_hir_analysis/messages.ftl

+11
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,17 @@ hir_analysis_unused_associated_type_bounds =
434434
.note = this associated type has a `where Self: Sized` bound. Thus, while the associated type can be specified, it cannot be used in any way, because trait objects are not `Sized`.
435435
.suggestion = remove this bound
436436
437+
hir_analysis_unused_generic_parameter =
438+
{$param_def_kind} `{$param_name}` is never used
439+
.label = unused {$param_def_kind}
440+
.const_param_help = if you intended `{$param_name}` to be a const parameter, use `const {$param_name}: /* Type */` instead
441+
hir_analysis_unused_generic_parameter_adt_help =
442+
consider removing `{$param_name}`, referring to it in a field, or using a marker such as `{$phantom_data}`
443+
hir_analysis_unused_generic_parameter_adt_no_phantom_data_help =
444+
consider removing `{$param_name}` or referring to it in a field
445+
hir_analysis_unused_generic_parameter_ty_alias_help =
446+
consider removing `{$param_name}` or referring to it in the body of the type alias
447+
437448
hir_analysis_value_of_associated_struct_already_specified =
438449
the value of the associated type `{$item_name}` in trait `{$def_path}` is already specified
439450
.label = re-bound here

compiler/rustc_hir_analysis/src/check/check.rs

+54-23
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
3131
use rustc_trait_selection::traits::{self, TraitEngine, TraitEngineExt as _};
3232
use rustc_type_ir::fold::TypeFoldable;
3333

34+
use std::cell::LazyCell;
3435
use std::ops::ControlFlow;
3536

3637
pub fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Abi) {
@@ -520,9 +521,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
520521
}
521522
}
522523
DefKind::TyAlias => {
523-
let pty_ty = tcx.type_of(def_id).instantiate_identity();
524-
let generics = tcx.generics_of(def_id);
525-
check_type_params_are_used(tcx, generics, pty_ty);
524+
check_type_alias_type_params_are_used(tcx, def_id);
526525
}
527526
DefKind::ForeignMod => {
528527
let it = tcx.hir().expect_item(def_id);
@@ -1269,28 +1268,51 @@ fn detect_discriminant_duplicate<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
12691268
}
12701269
}
12711270

1272-
pub(super) fn check_type_params_are_used<'tcx>(
1273-
tcx: TyCtxt<'tcx>,
1274-
generics: &ty::Generics,
1275-
ty: Ty<'tcx>,
1276-
) {
1277-
debug!("check_type_params_are_used(generics={:?}, ty={:?})", generics, ty);
1278-
1279-
assert_eq!(generics.parent, None);
1271+
fn check_type_alias_type_params_are_used<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {
1272+
if tcx.type_alias_is_lazy(def_id) {
1273+
// Since we compute the variances for lazy type aliases and already reject bivariant
1274+
// parameters as unused, we can and should skip this check for lazy type aliases.
1275+
return;
1276+
}
12801277

1278+
let generics = tcx.generics_of(def_id);
12811279
if generics.own_counts().types == 0 {
12821280
return;
12831281
}
12841282

1285-
let mut params_used = BitSet::new_empty(generics.params.len());
1286-
1283+
let ty = tcx.type_of(def_id).instantiate_identity();
12871284
if ty.references_error() {
1288-
// If there is already another error, do not emit
1289-
// an error for not using a type parameter.
1285+
// If there is already another error, do not emit an error for not using a type parameter.
12901286
assert!(tcx.dcx().has_errors().is_some());
12911287
return;
12921288
}
12931289

1290+
// Lazily calculated because it is only needed in case of an error.
1291+
let bounded_params = LazyCell::new(|| {
1292+
tcx.explicit_predicates_of(def_id)
1293+
.predicates
1294+
.iter()
1295+
.filter_map(|(predicate, span)| {
1296+
let bounded_ty = match predicate.kind().skip_binder() {
1297+
ty::ClauseKind::Trait(pred) => pred.trait_ref.self_ty(),
1298+
ty::ClauseKind::TypeOutlives(pred) => pred.0,
1299+
_ => return None,
1300+
};
1301+
if let ty::Param(param) = bounded_ty.kind() {
1302+
Some((param.index, span))
1303+
} else {
1304+
None
1305+
}
1306+
})
1307+
// FIXME: This assumes that elaborated `Sized` bounds come first (which does hold at the
1308+
// time of writing). This is a bit fragile since we later use the span to detect elaborated
1309+
// `Sized` bounds. If they came last for example, this would break `Trait + /*elab*/Sized`
1310+
// since it would overwrite the span of the user-written bound. This could be fixed by
1311+
// folding the spans with `Span::to` which requires a bit of effort I think.
1312+
.collect::<FxHashMap<_, _>>()
1313+
});
1314+
1315+
let mut params_used = BitSet::new_empty(generics.params.len());
12941316
for leaf in ty.walk() {
12951317
if let GenericArgKind::Type(leaf_ty) = leaf.unpack()
12961318
&& let ty::Param(param) = leaf_ty.kind()
@@ -1305,15 +1327,24 @@ pub(super) fn check_type_params_are_used<'tcx>(
13051327
&& let ty::GenericParamDefKind::Type { .. } = param.kind
13061328
{
13071329
let span = tcx.def_span(param.def_id);
1308-
struct_span_code_err!(
1309-
tcx.dcx(),
1330+
let param_name = Ident::new(param.name, span);
1331+
1332+
// The corresponding predicates are post-`Sized`-elaboration. Therefore we
1333+
// * check for emptiness to detect lone user-written `?Sized` bounds
1334+
// * compare the param span to the pred span to detect lone user-written `Sized` bounds
1335+
let has_explicit_bounds = bounded_params.is_empty()
1336+
|| (*bounded_params).get(&param.index).is_some_and(|&&pred_sp| pred_sp != span);
1337+
let const_param_help = (!has_explicit_bounds).then_some(());
1338+
1339+
let mut diag = tcx.dcx().create_err(errors::UnusedGenericParameter {
13101340
span,
1311-
E0091,
1312-
"type parameter `{}` is unused",
1313-
param.name,
1314-
)
1315-
.with_span_label(span, "unused type parameter")
1316-
.emit();
1341+
param_name,
1342+
param_def_kind: tcx.def_descr(param.def_id),
1343+
help: errors::UnusedGenericParameterHelp::TyAlias { param_name },
1344+
const_param_help,
1345+
});
1346+
diag.code(E0091);
1347+
diag.emit();
13171348
}
13181349
}
13191350
}

compiler/rustc_hir_analysis/src/check/wfcheck.rs

+33-31
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use crate::autoderef::Autoderef;
22
use crate::constrained_generic_params::{identify_constrained_generic_params, Parameter};
3+
use crate::errors;
34

45
use rustc_ast as ast;
56
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
6-
use rustc_errors::{
7-
codes::*, pluralize, struct_span_code_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
8-
};
7+
use rustc_errors::{codes::*, pluralize, struct_span_code_err, Applicability, ErrorGuaranteed};
98
use rustc_hir as hir;
109
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
1110
use rustc_hir::lang_items::LangItem;
@@ -21,7 +20,7 @@ use rustc_middle::ty::{
2120
};
2221
use rustc_middle::ty::{GenericArgKind, GenericArgs};
2322
use rustc_session::parse::feature_err;
24-
use rustc_span::symbol::{sym, Ident, Symbol};
23+
use rustc_span::symbol::{sym, Ident};
2524
use rustc_span::{Span, DUMMY_SP};
2625
use rustc_target::spec::abi::Abi;
2726
use rustc_trait_selection::regions::InferCtxtRegionExt;
@@ -1869,7 +1868,7 @@ fn check_variances_for_type_defn<'tcx>(
18691868
hir::ParamName::Error => {}
18701869
_ => {
18711870
let has_explicit_bounds = explicitly_bounded_params.contains(&parameter);
1872-
report_bivariance(tcx, hir_param, has_explicit_bounds);
1871+
report_bivariance(tcx, hir_param, has_explicit_bounds, item.kind);
18731872
}
18741873
}
18751874
}
@@ -1879,30 +1878,38 @@ fn report_bivariance(
18791878
tcx: TyCtxt<'_>,
18801879
param: &rustc_hir::GenericParam<'_>,
18811880
has_explicit_bounds: bool,
1881+
item_kind: ItemKind<'_>,
18821882
) -> ErrorGuaranteed {
1883-
let span = param.span;
1884-
let param_name = param.name.ident().name;
1885-
let mut err = error_392(tcx, span, param_name);
1886-
1887-
let suggested_marker_id = tcx.lang_items().phantom_data();
1888-
// Help is available only in presence of lang items.
1889-
let msg = if let Some(def_id) = suggested_marker_id {
1890-
format!(
1891-
"consider removing `{}`, referring to it in a field, or using a marker such as `{}`",
1892-
param_name,
1893-
tcx.def_path_str(def_id),
1894-
)
1895-
} else {
1896-
format!("consider removing `{param_name}` or referring to it in a field")
1883+
let param_name = param.name.ident();
1884+
1885+
let help = match item_kind {
1886+
ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) => {
1887+
if let Some(def_id) = tcx.lang_items().phantom_data() {
1888+
errors::UnusedGenericParameterHelp::Adt {
1889+
param_name,
1890+
phantom_data: tcx.def_path_str(def_id),
1891+
}
1892+
} else {
1893+
errors::UnusedGenericParameterHelp::AdtNoPhantomData { param_name }
1894+
}
1895+
}
1896+
ItemKind::TyAlias(..) => errors::UnusedGenericParameterHelp::TyAlias { param_name },
1897+
item_kind => bug!("report_bivariance: unexpected item kind: {item_kind:?}"),
18971898
};
1898-
err.help(msg);
18991899

1900-
if matches!(param.kind, hir::GenericParamKind::Type { .. }) && !has_explicit_bounds {
1901-
err.help(format!(
1902-
"if you intended `{param_name}` to be a const parameter, use `const {param_name}: usize` instead"
1903-
));
1904-
}
1905-
err.emit()
1900+
let const_param_help =
1901+
matches!(param.kind, hir::GenericParamKind::Type { .. } if !has_explicit_bounds)
1902+
.then_some(());
1903+
1904+
let mut diag = tcx.dcx().create_err(errors::UnusedGenericParameter {
1905+
span: param.span,
1906+
param_name,
1907+
param_def_kind: tcx.def_descr(param.def_id.to_def_id()),
1908+
help,
1909+
const_param_help,
1910+
});
1911+
diag.code(E0392);
1912+
diag.emit()
19061913
}
19071914

19081915
impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
@@ -1967,11 +1974,6 @@ fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalModDefId) -> Result<(), Error
19671974
res
19681975
}
19691976

1970-
fn error_392(tcx: TyCtxt<'_>, span: Span, param_name: Symbol) -> DiagnosticBuilder<'_> {
1971-
struct_span_code_err!(tcx.dcx(), span, E0392, "parameter `{param_name}` is never used")
1972-
.with_span_label(span, "unused parameter")
1973-
}
1974-
19751977
pub fn provide(providers: &mut Providers) {
19761978
*providers = Providers { check_mod_type_wf, check_well_formed, ..*providers };
19771979
}

compiler/rustc_hir_analysis/src/errors.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1511,3 +1511,27 @@ pub struct NotSupportedDelegation<'a> {
15111511
#[label]
15121512
pub callee_span: Span,
15131513
}
1514+
1515+
#[derive(Diagnostic)]
1516+
#[diag(hir_analysis_unused_generic_parameter)]
1517+
pub(crate) struct UnusedGenericParameter {
1518+
#[primary_span]
1519+
#[label]
1520+
pub span: Span,
1521+
pub param_name: Ident,
1522+
pub param_def_kind: &'static str,
1523+
#[subdiagnostic]
1524+
pub help: UnusedGenericParameterHelp,
1525+
#[help(hir_analysis_const_param_help)]
1526+
pub const_param_help: Option<()>,
1527+
}
1528+
1529+
#[derive(Subdiagnostic)]
1530+
pub(crate) enum UnusedGenericParameterHelp {
1531+
#[help(hir_analysis_unused_generic_parameter_adt_help)]
1532+
Adt { param_name: Ident, phantom_data: String },
1533+
#[help(hir_analysis_unused_generic_parameter_adt_no_phantom_data_help)]
1534+
AdtNoPhantomData { param_name: Ident },
1535+
#[help(hir_analysis_unused_generic_parameter_ty_alias_help)]
1536+
TyAlias { param_name: Ident },
1537+
}

tests/ui/const-generics/generic_arg_infer/infer-arg-test.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ error: expected identifier, found reserved identifier `_`
1010
LL | fn bad_infer_fn<_>() {}
1111
| ^ expected identifier, found reserved identifier
1212

13-
error[E0392]: parameter `_` is never used
13+
error[E0392]: type parameter `_` is never used
1414
--> $DIR/infer-arg-test.rs:7:17
1515
|
1616
LL | struct BadInfer<_>;
17-
| ^ unused parameter
17+
| ^ unused type parameter
1818
|
1919
= help: consider removing `_`, referring to it in a field, or using a marker such as `PhantomData`
20-
= help: if you intended `_` to be a const parameter, use `const _: usize` instead
20+
= help: if you intended `_` to be a const parameter, use `const _: /* Type */` instead
2121

2222
error[E0107]: struct takes 2 generic arguments but 3 generic arguments were supplied
2323
--> $DIR/infer-arg-test.rs:18:10

tests/ui/const-generics/issue-46511.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ LL | _a: [u8; std::mem::size_of::<&'a mut u8>()]
77
= note: lifetime parameters may not be used in const expressions
88
= help: add `#![feature(generic_const_exprs)]` to allow generic const expressions
99

10-
error[E0392]: parameter `'a` is never used
10+
error[E0392]: lifetime parameter `'a` is never used
1111
--> $DIR/issue-46511.rs:3:12
1212
|
1313
LL | struct Foo<'a>
14-
| ^^ unused parameter
14+
| ^^ unused lifetime parameter
1515
|
1616
= help: consider removing `'a`, referring to it in a field, or using a marker such as `PhantomData`
1717

tests/ui/const-generics/issues/issue-67375.min.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ LL | inner: [(); { [|_: &T| {}; 0].len() }],
77
= note: type parameters may not be used in const expressions
88
= help: add `#![feature(generic_const_exprs)]` to allow generic const expressions
99

10-
error[E0392]: parameter `T` is never used
10+
error[E0392]: type parameter `T` is never used
1111
--> $DIR/issue-67375.rs:5:12
1212
|
1313
LL | struct Bug<T> {
14-
| ^ unused parameter
14+
| ^ unused type parameter
1515
|
1616
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
17-
= help: if you intended `T` to be a const parameter, use `const T: usize` instead
17+
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
1818

1919
error: aborting due to 2 previous errors
2020

tests/ui/const-generics/issues/issue-67945-1.min.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ LL | let b = &*(&x as *const _ as *const S);
1616
= note: type parameters may not be used in const expressions
1717
= help: add `#![feature(generic_const_exprs)]` to allow generic const expressions
1818

19-
error[E0392]: parameter `S` is never used
19+
error[E0392]: type parameter `S` is never used
2020
--> $DIR/issue-67945-1.rs:7:12
2121
|
2222
LL | struct Bug<S> {
23-
| ^ unused parameter
23+
| ^ unused type parameter
2424
|
2525
= help: consider removing `S`, referring to it in a field, or using a marker such as `PhantomData`
26-
= help: if you intended `S` to be a const parameter, use `const S: usize` instead
26+
= help: if you intended `S` to be a const parameter, use `const S: /* Type */` instead
2727

2828
error: aborting due to 3 previous errors
2929

tests/ui/const-generics/issues/issue-67945-3.min.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ LL | let x: Option<S> = None;
77
= note: type parameters may not be used in const expressions
88
= help: add `#![feature(generic_const_exprs)]` to allow generic const expressions
99

10-
error[E0392]: parameter `S` is never used
10+
error[E0392]: type parameter `S` is never used
1111
--> $DIR/issue-67945-3.rs:9:12
1212
|
1313
LL | struct Bug<S> {
14-
| ^ unused parameter
14+
| ^ unused type parameter
1515
|
1616
= help: consider removing `S`, referring to it in a field, or using a marker such as `PhantomData`
17-
= help: if you intended `S` to be a const parameter, use `const S: usize` instead
17+
= help: if you intended `S` to be a const parameter, use `const S: /* Type */` instead
1818

1919
error: aborting due to 2 previous errors
2020

tests/ui/const-generics/issues/issue-67945-4.min.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ LL | let x: Option<Box<S>> = None;
77
= note: type parameters may not be used in const expressions
88
= help: add `#![feature(generic_const_exprs)]` to allow generic const expressions
99

10-
error[E0392]: parameter `S` is never used
10+
error[E0392]: type parameter `S` is never used
1111
--> $DIR/issue-67945-4.rs:8:12
1212
|
1313
LL | struct Bug<S> {
14-
| ^ unused parameter
14+
| ^ unused type parameter
1515
|
1616
= help: consider removing `S`, referring to it in a field, or using a marker such as `PhantomData`
17-
= help: if you intended `S` to be a const parameter, use `const S: usize` instead
17+
= help: if you intended `S` to be a const parameter, use `const S: /* Type */` instead
1818

1919
error: aborting due to 2 previous errors
2020

0 commit comments

Comments
 (0)