Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,61 @@ impl<'hir> Generics<'hir> {
bound_span.with_lo(bounds[bound_pos - 1].span().hi())
}
}

pub fn span_for_param_removal(&self, param_index: usize) -> Span {
if param_index >= self.params.len() {
return self.span.shrink_to_hi();
}

let is_impl_generic = |par: &&GenericParam<'_>| match par.kind {
GenericParamKind::Type { .. }
| GenericParamKind::Const { .. }
| GenericParamKind::Lifetime { kind: LifetimeParamKind::Explicit } => true,
_ => false,
};

// Find the span of the type parameter.
if let Some(next) = self.params[param_index + 1..].iter().find(is_impl_generic) {
self.params[param_index].span.until(next.span)
} else if let Some(prev) = self.params[..param_index].iter().rfind(is_impl_generic) {
let mut prev_span = prev.span;
// Consider the span of the bounds with the previous generic parameter when there is.
if let Some(prev_bounds_span) = self.span_for_param_bounds(prev) {
prev_span = prev_span.to(prev_bounds_span);
}

// Consider the span of the bounds with the current generic parameter when there is.
prev_span.shrink_to_hi().to(
if let Some(cur_bounds_span) = self.span_for_param_bounds(&self.params[param_index])
{
cur_bounds_span
} else {
self.params[param_index].span
},
)
} else {
// Remove also angle brackets <> when there is just ONE generic parameter.
self.span
}
}

fn span_for_param_bounds(&self, param: &GenericParam<'hir>) -> Option<Span> {
self.predicates
.iter()
.find(|pred| {
if let WherePredicateKind::BoundPredicate(WhereBoundPredicate {
origin: PredicateOrigin::GenericParam,
bounded_ty,
..
}) = pred.kind
{
bounded_ty.span == param.span
} else {
false
}
})
.map(|pred| pred.span)
}
}

/// A single predicate in a where-clause.
Expand Down
139 changes: 136 additions & 3 deletions compiler/rustc_hir_analysis/src/impl_wf_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@
//! fixed, but for the moment it's easier to do these checks early.

use std::assert_matches::debug_assert_matches;
use std::ops::ControlFlow;

use min_specialization::check_min_specialization;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_errors::codes::*;
use rustc_errors::{Applicability, Diag};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::{self, Visitor, walk_lifetime};
use rustc_hir::{HirId, LifetimeKind, Path, QPath, Ty, TyKind};
use rustc_middle::hir::nested_filter::All;
use rustc_middle::ty::{self, GenericParamDef, TyCtxt, TypeVisitableExt};
use rustc_span::{ErrorGuaranteed, kw};

use crate::constrained_generic_params as cgp;
Expand Down Expand Up @@ -172,6 +176,13 @@ pub(crate) fn enforce_impl_lifetime_params_are_constrained(
);
}
}
suggest_to_remove_or_use_generic(
tcx,
&mut diag,
impl_def_id,
param,
"lifetime",
);
res = Err(diag.emit());
}
}
Expand Down Expand Up @@ -235,8 +246,130 @@ pub(crate) fn enforce_impl_non_lifetime_params_are_constrained(
const_param_note2: const_param_note,
});
diag.code(E0207);
suggest_to_remove_or_use_generic(tcx, &mut diag, impl_def_id, &param, "type");
res = Err(diag.emit());
}
}
res
}

/// A HIR visitor that checks if a specific generic parameter (by its `DefId`)
/// is used within a given HIR tree.
struct ParamUsageVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
/// The `DefId` of the generic parameter we are looking for.
param_def_id: DefId,
found: bool,
}

// todo: maybe this can be done more efficiently by only searching for generics OR lifetimes and searching more effectively
impl<'tcx> Visitor<'tcx> for ParamUsageVisitor<'tcx> {
type NestedFilter = All;

fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}

/// We use `ControlFlow` to stop visiting as soon as we find what we're looking for.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the comments in this impl feel redundant

type Result = ControlFlow<()>;

/// This is the primary method for finding usages of type or const parameters.
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result {
if let Some(res_def_id) = path.res.opt_def_id() {
if res_def_id == self.param_def_id {
self.found = true;
return ControlFlow::Break(()); // Found it, stop visiting.
}
}
// If not found, continue walking down the HIR tree.
intravisit::walk_path(self, path)
}

fn visit_lifetime(&mut self, lifetime: &'tcx rustc_hir::Lifetime) -> Self::Result {
if let LifetimeKind::Param(id) = lifetime.kind {
if let Some(local_def_id) = self.param_def_id.as_local() {
if id == local_def_id {
self.found = true;
return ControlFlow::Break(()); // Found it, stop visiting.
}
}
}
walk_lifetime(self, lifetime)
}
}

fn suggest_to_remove_or_use_generic(
tcx: TyCtxt<'_>,
diag: &mut Diag<'_>,
impl_def_id: LocalDefId,
param: &GenericParamDef,
parameter_type: &str,
) {
let node = tcx.hir_node_by_def_id(impl_def_id);
let hir_impl = node.expect_item().expect_impl();

let Some((index, _)) = hir_impl
.generics
.params
.iter()
.enumerate()
.find(|(_, par)| par.def_id.to_def_id() == param.def_id)
else {
return;
};

// search if the parameter is used in the impl body
let mut visitor = ParamUsageVisitor {
tcx, // Pass the TyCtxt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tcx, // Pass the TyCtxt
tcx,

param_def_id: param.def_id,
found: false,
};

for item_ref in hir_impl.items {
let _ = visitor.visit_impl_item_ref(item_ref);
if visitor.found {
break;
}
}

let is_param_used = visitor.found;

// Suggestion for removing the type parameter.
let mut suggestions = vec![];
if !is_param_used {
// Only suggest removing it if it's not used anywhere.
suggestions.push(vec![(hir_impl.generics.span_for_param_removal(index), String::new())]);
}

// Suggestion for making use of the type parameter.
if let Some(path) = extract_ty_as_path(hir_impl.self_ty) {
let seg = path.segments.last().unwrap();
if let Some(args) = seg.args {
suggestions
.push(vec![(args.span().unwrap().shrink_to_hi(), format!(", {}", param.name))]);
} else {
suggestions.push(vec![(seg.ident.span.shrink_to_hi(), format!("<{}>", param.name))]);
}
}

let msg = if is_param_used {
// If it's used, the only valid fix is to constrain it.
format!("make use of the {} parameter `{}` in the `self` type", parameter_type, param.name)
} else {
format!(
"either remove the unused {} parameter `{}`, or make use of it",
parameter_type, param.name
)
};

diag.multipart_suggestions(msg, suggestions, Applicability::MaybeIncorrect);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use a multi-part suggestion for this, these should be multiple distinct suggestions

}

fn extract_ty_as_path<'hir>(ty: &Ty<'hir>) -> Option<&'hir Path<'hir>> {
match ty.kind {
TyKind::Path(QPath::Resolved(_, path)) => Some(path),
TyKind::Slice(ty) | TyKind::Array(ty, _) => extract_ty_as_path(ty),
TyKind::Ptr(ty) | TyKind::Ref(_, ty) => extract_ty_as_path(ty.ty),
_ => None,
}
}
8 changes: 8 additions & 0 deletions tests/rustdoc-ui/not-wf-ambiguous-normalization.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
|
LL | impl<T> Allocator for DefaultAllocator {
| ^ unconstrained type parameter
|
help: either remove the unused type parameter `T`, or make use of it
|
LL - impl<T> Allocator for DefaultAllocator {
LL + impl Allocator for DefaultAllocator {
|
LL | impl<T> Allocator for DefaultAllocator<T> {
| +++

error: aborting due to 1 previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ error[E0207]: the type parameter `Q` is not constrained by the impl trait, self
|
LL | unsafe impl<Q: Trait> Send for Inner {}
| ^ unconstrained type parameter
|
help: either remove the unused type parameter `Q`, or make use of it
|
LL - unsafe impl<Q: Trait> Send for Inner {}
LL + unsafe impl Send for Inner {}
|
LL | unsafe impl<Q: Trait> Send for Inner<Q> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only suggest this if it could be correct (maybe by checking if the self type has generic parameters), or we should be very clear that it's just illustrative:

help: if `Inner` takes generic parameters, consider instantiating it with `Q` to constrain `Q`
   |
LL - unsafe impl<Q: Trait> Send for Inner {}
LL + unsafe impl<Q: Trait> Send for Inner<Q> {}
   |                                     ^^^ `Q` could be used here

Copy link
Author

@TomtheCoder2 TomtheCoder2 Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well there would another error if the Inner struct took a generic parameter. I made a similar example here:

struct Foo<N> {x: N}

impl<N> Foo {}

which obviously gives the error, that the generic parameter N should be provided for the implementation of Foo:

error[E0107]: missing generics for struct `Foo`
 --> src/lib.rs:3:9
  |
3 | impl<N> Foo {}
  |         ^^^ expected 1 generic argument
  |
note: struct defined here, with 1 generic parameter: `N`
 --> src/lib.rs:1:8
  |
1 | struct Foo<N> {x: N}
  |        ^^^ -
help: add missing generic argument
  |
3 | impl<N> Foo<N> {}
  |            +++

For more information about this error, try `rustc --explain E0107`.

Therefore I would even suggest never suggesting to add the generic argument in general or did I miss some edgecase?
Or we could suggest adding it as a type parameter to the struct and to the impl

| +++

error: aborting due to 1 previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait,
|
LL | impl<'a> Foo<fn(&())> {
| ^^ unconstrained lifetime parameter
|
help: make use of the lifetime parameter `'a` in the `self` type
|
LL | impl<'a> Foo<fn(&()), 'a> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to above, we should make sure this is correct or clearly indicate that it is purely illustrative.

| ++++

error[E0308]: mismatched types
--> $DIR/hr-do-not-blame-outlives-static-ice.rs:14:11
Expand Down
10 changes: 10 additions & 0 deletions tests/ui/associated-types/issue-26262.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
|
LL | impl<T: Tr> S<T::Assoc> {
| ^ unconstrained type parameter
|
help: make use of the type parameter `T` in the `self` type
|
LL | impl<T: Tr> S<T::Assoc, T> {
| +++

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
--> $DIR/issue-26262.rs:17:6
|
LL | impl<'a,T: Trait2<'a>> Trait1<<T as Trait2<'a>>::Foo> for T {
| ^^ unconstrained lifetime parameter
|
help: make use of the lifetime parameter `'a` in the `self` type
|
LL | impl<'a,T: Trait2<'a>> Trait1<<T as Trait2<'a>>::Foo> for T<'a> {
| ++++

error: aborting due to 2 previous errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
|
LL | impl<T: ?Sized> Mirror for A {
| ^ unconstrained type parameter
|

error[E0277]: the trait bound `(dyn B + 'static): Mirror` is not satisfied
--> $DIR/projection-dyn-associated-type.rs:22:6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait,
|
LL | impl<'a> Fun for Holder {
| ^^ unconstrained lifetime parameter
|
help: make use of the lifetime parameter `'a` in the `self` type
|
LL | impl<'a> Fun for Holder<'a> {
| ++++

error: aborting due to 1 previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait,
|
LL | impl<'a> Actor for () {
| ^^ unconstrained lifetime parameter
|

error: aborting due to 1 previous error

Expand Down
7 changes: 7 additions & 0 deletions tests/ui/async-await/issues/issue-78654.full.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ LL | impl<const H: feature> Foo {
|
= note: expressions using a const parameter must map each value to a distinct output value
= note: proving the result of expressions other than the parameter are unique is not supported
help: either remove the unused type parameter `H`, or make use of it
|
LL - impl<const H: feature> Foo {
LL + impl Foo {
|
LL | impl<const H: feature> Foo<H> {
| +++

error: aborting due to 2 previous errors

Expand Down
7 changes: 7 additions & 0 deletions tests/ui/async-await/issues/issue-78654.min.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ LL | impl<const H: feature> Foo {
|
= note: expressions using a const parameter must map each value to a distinct output value
= note: proving the result of expressions other than the parameter are unique is not supported
help: either remove the unused type parameter `H`, or make use of it
|
LL - impl<const H: feature> Foo {
LL + impl Foo {
|
LL | impl<const H: feature> Foo<H> {
| +++

error: aborting due to 2 previous errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ error[E0207]: the type parameter `U` is not constrained by the impl trait, self
|
LL | impl<U> Trait for () where (U,): AssocConst<A = { 0 }> {}
| ^ unconstrained type parameter
|
help: either remove the unused type parameter `U`, or make use of it
|
LL - impl<U> Trait for () where (U,): AssocConst<A = { 0 }> {}
LL + impl Trait for () where (U,): AssocConst<A = { 0 }> {}
|

error[E0282]: type annotations needed
--> $DIR/unconstrained_impl_param.rs:21:5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ LL | impl<'a, const NUM: usize> std::ops::Add<&'a Foo> for Foo
|
= note: expressions using a const parameter must map each value to a distinct output value
= note: proving the result of expressions other than the parameter are unique is not supported
help: either remove the unused type parameter `NUM`, or make use of it
|
LL - impl<'a, const NUM: usize> std::ops::Add<&'a Foo> for Foo
LL + impl<'a> std::ops::Add<&'a Foo> for Foo
|
LL | impl<'a, const NUM: usize> std::ops::Add<&'a Foo> for Foo<NUM>
| +++++

error: aborting due to 3 previous errors; 1 warning emitted

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ LL | impl<'a, T, const N: usize> Iterator for ConstChunksExact<'a, T, {}> {
|
= note: expressions using a const parameter must map each value to a distinct output value
= note: proving the result of expressions other than the parameter are unique is not supported
help: make use of the type parameter `N` in the `self` type
|
LL | impl<'a, T, const N: usize> Iterator for ConstChunksExact<'a, T, {}, N> {
| +++

error: aborting due to 8 previous errors

Expand Down
Loading
Loading