Rollup merge of #127871 - compiler-errors:recursive, r=estebank

Mention that type parameters are used recursively on bivariance error

Right now when a type parameter is used recursively, even with indirection (so it has a finite size) we say that the type parameter is unused:

```
struct B<T>(Box<B<T>>);
```

This is confusing, because the type parameter is *used*, it just doesn't have its variance constrained. This PR tweaks that message to mention that it must be used *non-recursively*.

Not sure if we should actually mention "variance" here, but also I'd somewhat prefer we don't keep the power users in the dark w.r.t the real underlying issue, which is that the variance isn't constrained. That technical detail is reserved for a note, though.

cc `@fee1-dead`

Fixes #118976
Fixes #26283
Fixes #53191
Fixes #105740
Fixes #110466
This commit is contained in:
Matthias Krüger 2024-07-18 23:05:22 +02:00 committed by GitHub
commit 65de5d0472
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 211 additions and 31 deletions

View File

@ -382,6 +382,10 @@ hir_analysis_placeholder_not_allowed_item_signatures = the placeholder `_` is no
hir_analysis_precise_capture_self_alias = `Self` can't be captured in `use<...>` precise captures list, since it is an alias
.label = `Self` is not a generic argument, but an alias to the type of the {$what}
hir_analysis_recursive_generic_parameter = {$param_def_kind} `{$param_name}` is only used recursively
.label = {$param_def_kind} must be used non-recursively in the definition
.note = all type parameters must be used in a non-recursive way in order to constrain their variance
hir_analysis_redundant_lifetime_args = unnecessary lifetime parameter `{$victim}`
.note = you can use the `{$candidate}` lifetime directly, in place of `{$victim}`
@ -549,6 +553,8 @@ hir_analysis_unused_generic_parameter =
{$param_def_kind} `{$param_name}` is never used
.label = unused {$param_def_kind}
.const_param_help = if you intended `{$param_name}` to be a const parameter, use `const {$param_name}: /* Type */` instead
.usage_spans = `{$param_name}` is named here, but is likely unused in the containing type
hir_analysis_unused_generic_parameter_adt_help =
consider removing `{$param_name}`, referring to it in a field, or using a marker such as `{$phantom_data}`
hir_analysis_unused_generic_parameter_adt_no_phantom_data_help =

View File

@ -1572,6 +1572,7 @@ fn check_type_alias_type_params_are_used<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalD
param_name,
param_def_kind: tcx.def_descr(param.def_id),
help: errors::UnusedGenericParameterHelp::TyAlias { param_name },
usage_spans: vec![],
const_param_help,
});
diag.code(E0091);

View File

@ -4,12 +4,12 @@
use crate::errors;
use crate::fluent_generated as fluent;
use hir::intravisit::Visitor;
use hir::intravisit::{self, Visitor};
use rustc_ast as ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
use rustc_errors::{codes::*, pluralize, struct_span_code_err, Applicability, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
use rustc_hir::lang_items::LangItem;
use rustc_hir::ItemKind;
@ -1799,7 +1799,7 @@ fn receiver_is_implemented<'tcx>(
fn check_variances_for_type_defn<'tcx>(
tcx: TyCtxt<'tcx>,
item: &hir::Item<'tcx>,
item: &'tcx hir::Item<'tcx>,
hir_generics: &hir::Generics<'tcx>,
) {
let identity_args = ty::GenericArgs::identity_for_item(tcx, item.owner_id);
@ -1886,21 +1886,21 @@ fn check_variances_for_type_defn<'tcx>(
hir::ParamName::Error => {}
_ => {
let has_explicit_bounds = explicitly_bounded_params.contains(&parameter);
report_bivariance(tcx, hir_param, has_explicit_bounds, item.kind);
report_bivariance(tcx, hir_param, has_explicit_bounds, item);
}
}
}
}
fn report_bivariance(
tcx: TyCtxt<'_>,
param: &rustc_hir::GenericParam<'_>,
fn report_bivariance<'tcx>(
tcx: TyCtxt<'tcx>,
param: &'tcx hir::GenericParam<'tcx>,
has_explicit_bounds: bool,
item_kind: ItemKind<'_>,
item: &'tcx hir::Item<'tcx>,
) -> ErrorGuaranteed {
let param_name = param.name.ident();
let help = match item_kind {
let help = match item.kind {
ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) => {
if let Some(def_id) = tcx.lang_items().phantom_data() {
errors::UnusedGenericParameterHelp::Adt {
@ -1915,6 +1915,49 @@ fn report_bivariance(
item_kind => bug!("report_bivariance: unexpected item kind: {item_kind:?}"),
};
let mut usage_spans = vec![];
intravisit::walk_item(
&mut CollectUsageSpans { spans: &mut usage_spans, param_def_id: param.def_id.to_def_id() },
item,
);
if !usage_spans.is_empty() {
// First, check if the ADT is (probably) cyclical. We say probably here, since
// we're not actually looking into substitutions, just walking through fields.
// And we only recurse into the fields of ADTs, and not the hidden types of
// opaques or anything else fancy.
let item_def_id = item.owner_id.to_def_id();
let is_probably_cyclical = if matches!(
tcx.def_kind(item_def_id),
DefKind::Struct | DefKind::Union | DefKind::Enum
) {
IsProbablyCyclical { tcx, adt_def_id: item_def_id, seen: Default::default() }
.visit_all_fields(tcx.adt_def(item_def_id))
.is_break()
} else {
false
};
// If the ADT is cyclical, then if at least one usage of the type parameter or
// the `Self` alias is present in the, then it's probably a cyclical struct, and
// we should call those parameter usages recursive rather than just saying they're
// unused...
//
// We currently report *all* of the parameter usages, since computing the exact
// subset is very involved, and the fact we're mentioning recursion at all is
// likely to guide the user in the right direction.
if is_probably_cyclical {
let diag = tcx.dcx().create_err(errors::RecursiveGenericParameter {
spans: usage_spans,
param_span: param.span,
param_name,
param_def_kind: tcx.def_descr(param.def_id.to_def_id()),
help,
note: (),
});
return diag.emit();
}
}
let const_param_help =
matches!(param.kind, hir::GenericParamKind::Type { .. } if !has_explicit_bounds)
.then_some(());
@ -1923,6 +1966,7 @@ fn report_bivariance(
span: param.span,
param_name,
param_def_kind: tcx.def_descr(param.def_id.to_def_id()),
usage_spans,
help,
const_param_help,
});
@ -1930,6 +1974,77 @@ fn report_bivariance(
diag.emit()
}
/// Detects cases where an ADT is trivially cyclical -- we want to detect this so
/// /we only mention that its parameters are used cyclically if the ADT is truly
/// cyclical.
///
/// Notably, we don't consider substitutions here, so this may have false positives.
struct IsProbablyCyclical<'tcx> {
tcx: TyCtxt<'tcx>,
adt_def_id: DefId,
seen: FxHashSet<DefId>,
}
impl<'tcx> IsProbablyCyclical<'tcx> {
fn visit_all_fields(&mut self, adt_def: ty::AdtDef<'tcx>) -> ControlFlow<(), ()> {
for field in adt_def.all_fields() {
self.tcx.type_of(field.did).instantiate_identity().visit_with(self)?;
}
ControlFlow::Continue(())
}
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IsProbablyCyclical<'tcx> {
type Result = ControlFlow<(), ()>;
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<(), ()> {
if let Some(adt_def) = t.ty_adt_def() {
if adt_def.did() == self.adt_def_id {
return ControlFlow::Break(());
}
if self.seen.insert(adt_def.did()) {
self.visit_all_fields(adt_def)?;
}
}
t.super_visit_with(self)
}
}
/// Collect usages of the `param_def_id` and `Res::SelfTyAlias` in the HIR.
///
/// This is used to report places where the user has used parameters in a
/// non-variance-constraining way for better bivariance errors.
struct CollectUsageSpans<'a> {
spans: &'a mut Vec<Span>,
param_def_id: DefId,
}
impl<'tcx> Visitor<'tcx> for CollectUsageSpans<'_> {
type Result = ();
fn visit_generics(&mut self, _g: &'tcx rustc_hir::Generics<'tcx>) -> Self::Result {
// Skip the generics. We only care about fields, not where clause/param bounds.
}
fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) -> Self::Result {
if let hir::TyKind::Path(hir::QPath::Resolved(None, qpath)) = t.kind {
if let Res::Def(DefKind::TyParam, def_id) = qpath.res
&& def_id == self.param_def_id
{
self.spans.push(t.span);
return;
} else if let Res::SelfTyAlias { .. } = qpath.res {
self.spans.push(t.span);
return;
}
}
intravisit::walk_ty(self, t);
}
}
impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
/// Feature gates RFC 2056 -- trivial bounds, checking for global bounds that
/// aren't true.

View File

@ -1597,12 +1597,29 @@ pub(crate) struct UnusedGenericParameter {
pub span: Span,
pub param_name: Ident,
pub param_def_kind: &'static str,
#[label(hir_analysis_usage_spans)]
pub usage_spans: Vec<Span>,
#[subdiagnostic]
pub help: UnusedGenericParameterHelp,
#[help(hir_analysis_const_param_help)]
pub const_param_help: Option<()>,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_recursive_generic_parameter)]
pub(crate) struct RecursiveGenericParameter {
#[primary_span]
pub spans: Vec<Span>,
#[label]
pub param_span: Span,
pub param_name: Ident,
pub param_def_kind: &'static str,
#[subdiagnostic]
pub help: UnusedGenericParameterHelp,
#[note]
pub note: (),
}
#[derive(Subdiagnostic)]
pub(crate) enum UnusedGenericParameterHelp {
#[help(hir_analysis_unused_generic_parameter_adt_help)]

View File

@ -8,7 +8,9 @@ error[E0392]: type parameter `T` is never used
--> $DIR/inherent-impls-overflow.rs:14:12
|
LL | type Poly0<T> = Poly1<(T,)>;
| ^ unused type parameter
| ^ - `T` is named here, but is likely unused in the containing type
| |
| unused type parameter
|
= help: consider removing `T` or referring to it in the body of the type alias
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
@ -17,7 +19,9 @@ error[E0392]: type parameter `T` is never used
--> $DIR/inherent-impls-overflow.rs:17:12
|
LL | type Poly1<T> = Poly0<(T,)>;
| ^ unused type parameter
| ^ - `T` is named here, but is likely unused in the containing type
| |
| unused type parameter
|
= help: consider removing `T` or referring to it in the body of the type alias
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead

View File

@ -1,9 +1,9 @@
//~ ERROR overflow evaluating the requirement `A<A<A<A<A<A<A<...>>>>>>>: Send`
struct A<T>(B<T>);
//~^ ERROR recursive types `A` and `B` have infinite size
//~| ERROR `T` is never used
//~| ERROR `T` is only used recursively
struct B<T>(A<A<T>>);
//~^ ERROR `T` is never used
//~^ ERROR `T` is only used recursively
trait Foo {}
impl<T> Foo for T where T: Send {}
impl Foo for B<u8> {}

View File

@ -15,23 +15,27 @@ LL |
LL ~ struct B<T>(Box<A<A<T>>>);
|
error[E0392]: type parameter `T` is never used
--> $DIR/issue-105231.rs:2:10
error: type parameter `T` is only used recursively
--> $DIR/issue-105231.rs:2:15
|
LL | struct A<T>(B<T>);
| ^ unused type parameter
| - ^
| |
| type parameter must be used non-recursively in the definition
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
= note: all type parameters must be used in a non-recursive way in order to constrain their variance
error[E0392]: type parameter `T` is never used
--> $DIR/issue-105231.rs:5:10
error: type parameter `T` is only used recursively
--> $DIR/issue-105231.rs:5:17
|
LL | struct B<T>(A<A<T>>);
| ^ unused type parameter
| - ^
| |
| type parameter must be used non-recursively in the definition
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
= note: all type parameters must be used in a non-recursive way in order to constrain their variance
error[E0275]: overflow evaluating the requirement `A<A<A<A<A<A<A<...>>>>>>>: Send`
|
@ -44,5 +48,5 @@ LL | struct B<T>(A<A<T>>);
error: aborting due to 4 previous errors
Some errors have detailed explanations: E0072, E0275, E0392.
Some errors have detailed explanations: E0072, E0275.
For more information about an error, try `rustc --explain E0072`.

View File

@ -11,11 +11,14 @@ enum SomeEnum<A> { Nothing }
// Here T might *appear* used, but in fact it isn't.
enum ListCell<T> {
//~^ ERROR parameter `T` is never used
Cons(Box<ListCell<T>>),
//~^ ERROR parameter `T` is only used recursively
Nil
}
struct SelfTyAlias<T>(Box<Self>);
//~^ ERROR parameter `T` is only used recursively
struct WithBounds<T: Sized> {}
//~^ ERROR parameter `T` is never used
@ -25,4 +28,9 @@ struct WithWhereBounds<T> where T: Sized {}
struct WithOutlivesBounds<T: 'static> {}
//~^ ERROR parameter `T` is never used
struct DoubleNothing<T> {
//~^ ERROR parameter `T` is never used
s: SomeStruct<T>,
}
fn main() {}

View File

@ -16,17 +16,30 @@ LL | enum SomeEnum<A> { Nothing }
= help: consider removing `A`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `A` to be a const parameter, use `const A: /* Type */` instead
error[E0392]: type parameter `T` is never used
--> $DIR/variance-unused-type-param.rs:13:15
error: type parameter `T` is only used recursively
--> $DIR/variance-unused-type-param.rs:14:23
|
LL | enum ListCell<T> {
| ^ unused type parameter
| - type parameter must be used non-recursively in the definition
LL | Cons(Box<ListCell<T>>),
| ^
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
= note: all type parameters must be used in a non-recursive way in order to constrain their variance
error: type parameter `T` is only used recursively
--> $DIR/variance-unused-type-param.rs:19:27
|
LL | struct SelfTyAlias<T>(Box<Self>);
| - ^^^^
| |
| type parameter must be used non-recursively in the definition
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
= note: all type parameters must be used in a non-recursive way in order to constrain their variance
error[E0392]: type parameter `T` is never used
--> $DIR/variance-unused-type-param.rs:19:19
--> $DIR/variance-unused-type-param.rs:22:19
|
LL | struct WithBounds<T: Sized> {}
| ^ unused type parameter
@ -34,7 +47,7 @@ LL | struct WithBounds<T: Sized> {}
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
error[E0392]: type parameter `T` is never used
--> $DIR/variance-unused-type-param.rs:22:24
--> $DIR/variance-unused-type-param.rs:25:24
|
LL | struct WithWhereBounds<T> where T: Sized {}
| ^ unused type parameter
@ -42,13 +55,25 @@ LL | struct WithWhereBounds<T> where T: Sized {}
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
error[E0392]: type parameter `T` is never used
--> $DIR/variance-unused-type-param.rs:25:27
--> $DIR/variance-unused-type-param.rs:28:27
|
LL | struct WithOutlivesBounds<T: 'static> {}
| ^ unused type parameter
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
error: aborting due to 6 previous errors
error[E0392]: type parameter `T` is never used
--> $DIR/variance-unused-type-param.rs:31:22
|
LL | struct DoubleNothing<T> {
| ^ unused type parameter
LL |
LL | s: SomeStruct<T>,
| - `T` is named here, but is likely unused in the containing type
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead
error: aborting due to 8 previous errors
For more information about this error, try `rustc --explain E0392`.