Skip to main content

rustc_hir_analysis/errors/
remove_or_use_generic.rs

1use std::ops::ControlFlow;
2
3use rustc_errors::{Applicability, Diag};
4use rustc_hir::def::DefKind;
5use rustc_hir::def_id::{DefId, LocalDefId};
6use rustc_hir::intravisit::{self, Visitor, walk_lifetime};
7use rustc_hir::{GenericArg, HirId, LifetimeKind, Path, QPath, TyKind};
8use rustc_middle::hir::nested_filter::All;
9use rustc_middle::ty::{GenericParamDef, GenericParamDefKind, TyCtxt};
10
11use crate::hir::def::Res;
12
13/// Use a Visitor to find usages of the type or lifetime parameter
14struct ParamUsageVisitor<'tcx> {
15    tcx: TyCtxt<'tcx>,
16    /// The `DefId` of the generic parameter we are looking for.
17    param_def_id: DefId,
18    found: bool,
19}
20
21impl<'tcx> Visitor<'tcx> for ParamUsageVisitor<'tcx> {
22    type NestedFilter = All;
23
24    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
25        self.tcx
26    }
27
28    type Result = ControlFlow<()>;
29
30    fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result {
31        if let Some(res_def_id) = path.res.opt_def_id() {
32            if res_def_id == self.param_def_id {
33                self.found = true;
34                return ControlFlow::Break(());
35            }
36        }
37        intravisit::walk_path(self, path)
38    }
39
40    fn visit_lifetime(&mut self, lifetime: &'tcx rustc_hir::Lifetime) -> Self::Result {
41        if let LifetimeKind::Param(id) = lifetime.kind {
42            if let Some(local_def_id) = self.param_def_id.as_local() {
43                if id == local_def_id {
44                    self.found = true;
45                    return ControlFlow::Break(());
46                }
47            }
48        }
49        walk_lifetime(self, lifetime)
50    }
51}
52
53/// Adds a suggestion to a diagnostic to either remove an unused generic parameter, or use it.
54///
55/// # Examples
56///
57/// - `impl<T> Struct { ... }` where `T` is unused -> suggests removing `T` or using it.
58/// - `impl<T> Struct { // T used in here }` where `T` is used in the body but not in the self type -> suggests adding `T` to the self type and struct definition.
59/// - `impl<T> Struct { ... }` where the struct has a generic parameter with a default -> suggests adding `T` to the self type.
60pub(crate) fn suggest_to_remove_or_use_generic(
61    tcx: TyCtxt<'_>,
62    diag: &mut Diag<'_>,
63    impl_def_id: LocalDefId,
64    param: &GenericParamDef,
65    is_lifetime: bool,
66) {
67    let node = tcx.hir_node_by_def_id(impl_def_id);
68    let hir_impl = node.expect_item().expect_impl();
69
70    let Some((index, _)) = hir_impl
71        .generics
72        .params
73        .iter()
74        .enumerate()
75        .find(|(_, par)| par.def_id.to_def_id() == param.def_id)
76    else {
77        return;
78    };
79
80    // Get the Struct/ADT definition ID from the self type
81    let struct_def_id = if let TyKind::Path(QPath::Resolved(_, path)) = hir_impl.self_ty.kind
82        && let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, def_id) = path.res
83    {
84        def_id
85    } else {
86        return;
87    };
88
89    // Count how many generic parameters are defined in the struct definition
90    let generics = tcx.generics_of(struct_def_id);
91    let total_params = generics
92        .own_params
93        .iter()
94        .filter(|p| {
95            if is_lifetime {
96                #[allow(non_exhaustive_omitted_patterns)] match p.kind {
    GenericParamDefKind::Lifetime => true,
    _ => false,
}matches!(p.kind, GenericParamDefKind::Lifetime)
97            } else {
98                #[allow(non_exhaustive_omitted_patterns)] match p.kind {
    GenericParamDefKind::Type { .. } => true,
    _ => false,
}matches!(p.kind, GenericParamDefKind::Type { .. })
99            }
100        })
101        .count();
102
103    // Count how many arguments are currently provided in the impl
104    let mut provided_params = 0;
105    let mut last_segment_args = None;
106
107    if let TyKind::Path(QPath::Resolved(_, path)) = hir_impl.self_ty.kind
108        && let Some(seg) = path.segments.last()
109        && let Some(args) = seg.args
110    {
111        last_segment_args = Some(args);
112        provided_params = args
113            .args
114            .iter()
115            .filter(|arg| match arg {
116                GenericArg::Lifetime(_) => is_lifetime,
117                GenericArg::Type(_) => !is_lifetime,
118                _ => false,
119            })
120            .count();
121    }
122
123    let mut visitor = ParamUsageVisitor { tcx, param_def_id: param.def_id, found: false };
124    for item_ref in hir_impl.items {
125        let _ = visitor.visit_impl_item_ref(item_ref);
126        if visitor.found {
127            break;
128        }
129    }
130    let is_param_used = visitor.found;
131
132    let mut suggestions = ::alloc::vec::Vec::new()vec![];
133
134    // Option A: Remove (Only if not used in body)
135    if !is_param_used {
136        suggestions.push((hir_impl.generics.span_for_param_removal(index), String::new()));
137    }
138
139    // Option B: Suggest adding only if there's an available parameter in the struct definition
140    // or the parameter is already used somewhere, then we suggest adding to the impl struct and the struct definition
141    if provided_params < total_params || is_param_used {
142        if let Some(args) = last_segment_args {
143            // Struct already has <...>, append to it
144            suggestions.push((args.span().unwrap().shrink_to_hi(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", {0}", param.name))
    })format!(", {}", param.name)));
145        } else if let TyKind::Path(QPath::Resolved(_, path)) = hir_impl.self_ty.kind {
146            // Struct has no <...> yet, add it
147            let seg = path.segments.last().unwrap();
148            suggestions.push((seg.ident.span.shrink_to_hi(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("<{0}>", param.name))
    })format!("<{}>", param.name)));
149        }
150        if is_param_used {
151            // If the parameter is used in the body, we also want to suggest adding it to the struct definition if it's not already there
152            let struct_span = tcx.def_span(struct_def_id);
153            let last_param_span = if let Some(local_def_id) = struct_def_id.as_local() {
154                let hir_struct = tcx.hir_node_by_def_id(local_def_id).expect_item().expect_struct();
155                hir_struct.1.params.last().map(|param| param.span)
156            } else {
157                let generics = tcx.generics_of(struct_def_id);
158                generics.own_params.last().map(|param| tcx.def_span(param.def_id))
159            };
160
161            if let Some(last_param_span) = last_param_span {
162                suggestions.push((last_param_span.shrink_to_hi(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", {0}", param.name))
    })format!(", {}", param.name)));
163            } else {
164                suggestions.push((struct_span.shrink_to_hi(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("<{0}>", param.name))
    })format!("<{}>", param.name)));
165            }
166        }
167    }
168
169    if suggestions.is_empty() {
170        return;
171    }
172
173    let parameter_type = if is_lifetime { "lifetime" } else { "type" };
174    if is_param_used {
175        let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("use the {0} parameter `{1}` in the `{2}` type and use it in the type definition",
                parameter_type, param.name, tcx.def_path_str(struct_def_id)))
    })format!(
176            "use the {} parameter `{}` in the `{}` type and use it in the type definition",
177            parameter_type,
178            param.name,
179            tcx.def_path_str(struct_def_id)
180        );
181        diag.multipart_suggestion(
182            msg,
183            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(suggestions[0].0, suggestions[0].1.clone()),
                (suggestions[1].0, suggestions[1].1.clone())]))vec![
184                (suggestions[0].0, suggestions[0].1.clone()),
185                (suggestions[1].0, suggestions[1].1.clone()),
186            ],
187            Applicability::MaybeIncorrect,
188        );
189    } else {
190        let msg = if suggestions.len() == 2 {
191            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("either remove the unused {0} parameter `{1}`",
                parameter_type, param.name))
    })format!("either remove the unused {} parameter `{}`", parameter_type, param.name)
192        } else {
193            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("remove the unused {0} parameter `{1}`",
                parameter_type, param.name))
    })format!("remove the unused {} parameter `{}`", parameter_type, param.name)
194        };
195        diag.span_suggestion(
196            suggestions[0].0,
197            msg,
198            suggestions[0].1.clone(),
199            Applicability::MaybeIncorrect,
200        );
201        if suggestions.len() == 2 {
202            let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("or use it"))
    })format!("or use it");
203            diag.span_suggestion(
204                suggestions[1].0,
205                msg,
206                suggestions[1].1.clone(),
207                Applicability::MaybeIncorrect,
208            );
209        }
210    };
211}