Skip to main content

rustc_hir_analysis/
impl_wf_check.rs

1//! This pass enforces various "well-formedness constraints" on impls.
2//! Logically, it is part of wfcheck -- but we do it early so that we
3//! can stop compilation afterwards, since part of the trait matching
4//! infrastructure gets very grumpy if these conditions don't hold. In
5//! particular, if there are type parameters that are not part of the
6//! impl, then coherence will report strange inference ambiguity
7//! errors; if impls have duplicate items, we get misleading
8//! specialization errors. These things can (and probably should) be
9//! fixed, but for the moment it's easier to do these checks early.
10
11use std::debug_assert_matches;
12
13use min_specialization::check_min_specialization;
14use rustc_data_structures::fx::FxHashSet;
15use rustc_errors::Applicability;
16use rustc_errors::codes::*;
17use rustc_hir::def::DefKind;
18use rustc_hir::def_id::LocalDefId;
19use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, Unnormalized};
20use rustc_span::{ErrorGuaranteed, kw};
21
22use crate::constrained_generic_params as cgp;
23use crate::errors::UnconstrainedGenericParameter;
24use crate::errors::remove_or_use_generic::suggest_to_remove_or_use_generic;
25
26mod min_specialization;
27
28/// Checks that all the type/lifetime parameters on an impl also
29/// appear in the trait ref or self type (or are constrained by a
30/// where-clause). These rules are needed to ensure that, given a
31/// trait ref like `<T as Trait<U>>`, we can derive the values of all
32/// parameters on the impl (which is needed to make specialization
33/// possible).
34///
35/// However, in the case of lifetimes, we only enforce these rules if
36/// the lifetime parameter is used in an associated type. This is a
37/// concession to backwards compatibility; see comment at the end of
38/// the fn for details.
39///
40/// Example:
41///
42/// ```rust,ignore (pseudo-Rust)
43/// impl<T> Trait<Foo> for Bar { ... }
44/// //   ^ T does not appear in `Foo` or `Bar`, error!
45///
46/// impl<T> Trait<Foo<T>> for Bar { ... }
47/// //   ^ T appears in `Foo<T>`, ok.
48///
49/// impl<T> Trait<Foo> for Bar where Bar: Iterator<Item = T> { ... }
50/// //   ^ T is bound to `<Bar as Iterator>::Item`, ok.
51///
52/// impl<'a> Trait<Foo> for Bar { }
53/// //   ^ 'a is unused, but for back-compat we allow it
54///
55/// impl<'a> Trait<Foo> for Bar { type X = &'a i32; }
56/// //   ^ 'a is unused and appears in assoc type, error
57/// ```
58pub(crate) fn check_impl_wf(
59    tcx: TyCtxt<'_>,
60    impl_def_id: LocalDefId,
61    of_trait: bool,
62) -> Result<(), ErrorGuaranteed> {
63    if true {
    {
        match tcx.def_kind(impl_def_id) {
            DefKind::Impl { .. } => {}
            ref left_val => {
                ::core::panicking::assert_matches_failed(left_val,
                    "DefKind::Impl { .. }", ::core::option::Option::None);
            }
        }
    };
};debug_assert_matches!(tcx.def_kind(impl_def_id), DefKind::Impl { .. });
64
65    // Check that the args are constrained. We queryfied the check for ty/const params
66    // since unconstrained type/const params cause ICEs in projection, so we want to
67    // detect those specifically and project those to `TyKind::Error`.
68    let mut res = tcx.ensure_result().enforce_impl_non_lifetime_params_are_constrained(impl_def_id);
69    res = res.and(enforce_impl_lifetime_params_are_constrained(tcx, impl_def_id, of_trait));
70
71    if of_trait && tcx.features().min_specialization() {
72        res = res.and(check_min_specialization(tcx, impl_def_id));
73    }
74    res
75}
76
77pub(crate) fn enforce_impl_lifetime_params_are_constrained(
78    tcx: TyCtxt<'_>,
79    impl_def_id: LocalDefId,
80    of_trait: bool,
81) -> Result<(), ErrorGuaranteed> {
82    let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
83
84    // Don't complain about unconstrained type params when self ty isn't known due to errors.
85    // (#36836)
86    impl_self_ty.error_reported()?;
87
88    let impl_generics = tcx.generics_of(impl_def_id);
89    let impl_predicates = tcx.predicates_of(impl_def_id);
90    let impl_trait_ref =
91        of_trait.then(|| tcx.impl_trait_ref(impl_def_id).instantiate_identity().skip_norm_wip());
92
93    impl_trait_ref.error_reported()?;
94
95    let mut input_parameters = cgp::parameters_for_impl(tcx, impl_self_ty, impl_trait_ref);
96    cgp::identify_constrained_generic_params(
97        tcx,
98        impl_predicates,
99        impl_trait_ref,
100        &mut input_parameters,
101    );
102
103    // Disallow unconstrained lifetimes, but only if they appear in assoc types.
104    let lifetimes_in_associated_types: FxHashSet<_> = tcx
105        .associated_item_def_ids(impl_def_id)
106        .iter()
107        .flat_map(|&def_id| {
108            let item = tcx.associated_item(def_id);
109            match item.kind {
110                ty::AssocKind::Type { .. } => {
111                    if item.defaultness(tcx).has_value() {
112                        cgp::parameters_for(
113                            tcx,
114                            tcx.type_of(def_id).instantiate_identity().skip_norm_wip(),
115                            true,
116                        )
117                    } else {
118                        ::alloc::vec::Vec::new()vec![]
119                    }
120                }
121                ty::AssocKind::Fn { .. } | ty::AssocKind::Const { .. } => ::alloc::vec::Vec::new()vec![],
122            }
123        })
124        .collect();
125
126    let mut res = Ok(());
127    for param in &impl_generics.own_params {
128        match param.kind {
129            ty::GenericParamDefKind::Lifetime => {
130                // This is a horrible concession to reality. I think it'd be
131                // better to just ban unconstrained lifetimes outright, but in
132                // practice people do non-hygienic macros like:
133                //
134                // ```
135                // macro_rules! __impl_slice_eq1 {
136                //   ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
137                //     impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
138                //        ....
139                //     }
140                //   }
141                // }
142                // ```
143                //
144                // In a concession to backwards compatibility, we continue to
145                // permit those, so long as the lifetimes aren't used in
146                // associated types. I believe this is sound, because lifetimes
147                // used elsewhere are not projected back out.
148                let param_lt = cgp::Parameter::from(param.to_early_bound_region_data());
149                if lifetimes_in_associated_types.contains(&param_lt)
150                    && !input_parameters.contains(&param_lt)
151                {
152                    let mut diag = tcx.dcx().create_err(UnconstrainedGenericParameter {
153                        span: tcx.def_span(param.def_id),
154                        param_name: tcx.item_ident(param.def_id),
155                        param_def_kind: tcx.def_descr(param.def_id),
156                        const_param_note: false,
157                        const_param_note2: false,
158                    });
159                    diag.code(E0207);
160                    for p in &impl_generics.own_params {
161                        if p.name == kw::UnderscoreLifetime {
162                            let span = tcx.def_span(p.def_id);
163                            let Ok(snippet) = tcx.sess.source_map().span_to_snippet(span) else {
164                                continue;
165                            };
166
167                            let (span, sugg) = if &snippet == "'_" {
168                                (span, param.name.to_string())
169                            } else {
170                                (span.shrink_to_hi(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} ", param.name))
    })format!("{} ", param.name))
171                            };
172                            diag.span_suggestion_verbose(
173                                span,
174                                "consider using the named lifetime here instead of an implicit \
175                                 lifetime",
176                                sugg,
177                                Applicability::MaybeIncorrect,
178                            );
179                        }
180                    }
181                    suggest_to_remove_or_use_generic(tcx, &mut diag, impl_def_id, param, true);
182                    res = Err(diag.emit());
183                }
184            }
185            ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Const { .. } => {
186                // Enforced in `enforce_impl_non_lifetime_params_are_constrained`.
187            }
188        }
189    }
190    res
191}
192
193pub(crate) fn enforce_impl_non_lifetime_params_are_constrained(
194    tcx: TyCtxt<'_>,
195    impl_def_id: LocalDefId,
196) -> Result<(), ErrorGuaranteed> {
197    let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip();
198
199    // Don't complain about unconstrained type params when self ty isn't known due to errors.
200    // (#36836)
201    impl_self_ty.error_reported()?;
202
203    let impl_generics = tcx.generics_of(impl_def_id);
204    let impl_predicates = tcx.predicates_of(impl_def_id);
205    let impl_trait_ref = tcx
206        .impl_opt_trait_ref(impl_def_id)
207        .map(ty::EarlyBinder::instantiate_identity)
208        .map(Unnormalized::skip_norm_wip);
209
210    impl_trait_ref.error_reported()?;
211
212    let mut input_parameters = cgp::parameters_for_impl(tcx, impl_self_ty, impl_trait_ref);
213    cgp::identify_constrained_generic_params(
214        tcx,
215        impl_predicates,
216        impl_trait_ref,
217        &mut input_parameters,
218    );
219
220    let mut res = Ok(());
221    for param in &impl_generics.own_params {
222        let err = match param.kind {
223            // Disallow ANY unconstrained type parameters.
224            ty::GenericParamDefKind::Type { .. } => {
225                let param_ty = ty::ParamTy::for_def(param);
226                !input_parameters.contains(&cgp::Parameter::from(param_ty))
227            }
228            ty::GenericParamDefKind::Const { .. } => {
229                let param_ct = ty::ParamConst::for_def(param);
230                !input_parameters.contains(&cgp::Parameter::from(param_ct))
231            }
232            ty::GenericParamDefKind::Lifetime => {
233                // Enforced in `enforce_impl_type_params_are_constrained`.
234                false
235            }
236        };
237        if err {
238            let const_param_note = #[allow(non_exhaustive_omitted_patterns)] match param.kind {
    ty::GenericParamDefKind::Const { .. } => true,
    _ => false,
}matches!(param.kind, ty::GenericParamDefKind::Const { .. });
239            let mut diag = tcx.dcx().create_err(UnconstrainedGenericParameter {
240                span: tcx.def_span(param.def_id),
241                param_name: tcx.item_ident(param.def_id),
242                param_def_kind: tcx.def_descr(param.def_id),
243                const_param_note,
244                const_param_note2: const_param_note,
245            });
246            diag.code(E0207);
247            suggest_to_remove_or_use_generic(tcx, &mut diag, impl_def_id, &param, false);
248            res = Err(diag.emit());
249        }
250    }
251    res
252}