rustc_hir_analysis/check/
compare_eii.rs

1//! This module is very similar to `compare_impl_item`.
2//! Most logic is taken from there,
3//! since in a very similar way we're comparing some declaration of a signature to an implementation.
4//! The major difference is that we don't bother with self types, since for EIIs we're comparing freestanding item.
5
6use std::borrow::Cow;
7use std::iter;
8
9use rustc_data_structures::fx::FxIndexSet;
10use rustc_errors::{Applicability, E0806, struct_span_code_err};
11use rustc_hir::def_id::{DefId, LocalDefId};
12use rustc_hir::{self as hir, FnSig, HirId, ItemKind};
13use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
14use rustc_infer::traits::{ObligationCause, ObligationCauseCode};
15use rustc_middle::ty::error::{ExpectedFound, TypeError};
16use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode};
17use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
18use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
19use rustc_trait_selection::regions::InferCtxtRegionExt;
20use rustc_trait_selection::traits::{self, ObligationCtxt};
21use tracing::{debug, instrument};
22
23use super::potentially_plural_count;
24use crate::check::compare_impl_item::{
25    CheckNumberOfEarlyBoundRegionsError, check_number_of_early_bound_regions,
26};
27use crate::errors::{EiiWithGenerics, LifetimesOrBoundsMismatchOnEii};
28
29/// Checks whether the signature of some `external_impl`, matches
30/// the signature of `declaration`, which it is supposed to be compatible
31/// with in order to implement the item.
32pub(crate) fn compare_eii_function_types<'tcx>(
33    tcx: TyCtxt<'tcx>,
34    external_impl: LocalDefId,
35    declaration: DefId,
36    eii_name: Symbol,
37    eii_attr_span: Span,
38) -> Result<(), ErrorGuaranteed> {
39    check_is_structurally_compatible(tcx, external_impl, declaration, eii_name, eii_attr_span)?;
40
41    let external_impl_span = tcx.def_span(external_impl);
42    let cause = ObligationCause::new(
43        external_impl_span,
44        external_impl,
45        ObligationCauseCode::CompareEii { external_impl, declaration },
46    );
47
48    // FIXME(eii): even if we don't support generic functions, we should support explicit outlive bounds here
49    let param_env = tcx.param_env(declaration);
50
51    let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis());
52    let ocx = ObligationCtxt::new_with_diagnostics(infcx);
53
54    // We now need to check that the signature of the implementation is
55    // compatible with that of the declaration. We do this by
56    // checking that `impl_fty <: trait_fty`.
57    //
58    // FIXME: We manually instantiate the declaration here as we need
59    // to manually compute its implied bounds. Otherwise this could just
60    // be ocx.sub(impl_sig, trait_sig).
61
62    let mut wf_tys = FxIndexSet::default();
63    let norm_cause = ObligationCause::misc(external_impl_span, external_impl);
64
65    let declaration_sig = tcx.fn_sig(declaration).instantiate_identity();
66    let declaration_sig = tcx.liberate_late_bound_regions(external_impl.into(), declaration_sig);
67    debug!(?declaration_sig);
68
69    let unnormalized_external_impl_sig = infcx.instantiate_binder_with_fresh_vars(
70        external_impl_span,
71        infer::BoundRegionConversionTime::HigherRankedType,
72        tcx.fn_sig(external_impl).instantiate(
73            tcx,
74            infcx.fresh_args_for_item(external_impl_span, external_impl.to_def_id()),
75        ),
76    );
77    let external_impl_sig = ocx.normalize(&norm_cause, param_env, unnormalized_external_impl_sig);
78    debug!(?external_impl_sig);
79
80    // Next, add all inputs and output as well-formed tys. Importantly,
81    // we have to do this before normalization, since the normalized ty may
82    // not contain the input parameters. See issue #87748.
83    wf_tys.extend(declaration_sig.inputs_and_output.iter());
84    let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig);
85    // We also have to add the normalized declaration
86    // as we don't normalize during implied bounds computation.
87    wf_tys.extend(external_impl_sig.inputs_and_output.iter());
88
89    // FIXME: Copied over from compare impl items, same issue:
90    // We'd want to keep more accurate spans than "the method signature" when
91    // processing the comparison between the trait and impl fn, but we sadly lose them
92    // and point at the whole signature when a trait bound or specific input or output
93    // type would be more appropriate. In other places we have a `Vec<Span>`
94    // corresponding to their `Vec<Predicate>`, but we don't have that here.
95    // Fixing this would improve the output of test `issue-83765.rs`.
96    let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig);
97
98    if let Err(terr) = result {
99        debug!(?external_impl_sig, ?declaration_sig, ?terr, "sub_types failed");
100
101        let emitted = report_eii_mismatch(
102            infcx,
103            cause,
104            param_env,
105            terr,
106            (declaration, declaration_sig),
107            (external_impl, external_impl_sig),
108            eii_attr_span,
109            eii_name,
110        );
111        return Err(emitted);
112    }
113
114    if !(declaration_sig, external_impl_sig).references_error() {
115        for ty in unnormalized_external_impl_sig.inputs_and_output {
116            ocx.register_obligation(traits::Obligation::new(
117                infcx.tcx,
118                cause.clone(),
119                param_env,
120                ty::ClauseKind::WellFormed(ty.into()),
121            ));
122        }
123    }
124
125    // Check that all obligations are satisfied by the implementation's
126    // version.
127    let errors = ocx.evaluate_obligations_error_on_ambiguity();
128    if !errors.is_empty() {
129        let reported = infcx.err_ctxt().report_fulfillment_errors(errors);
130        return Err(reported);
131    }
132
133    // Finally, resolve all regions. This catches wily misuses of
134    // lifetime parameters.
135    let errors = infcx.resolve_regions(external_impl, param_env, wf_tys);
136    if !errors.is_empty() {
137        return Err(infcx
138            .tainted_by_errors()
139            .unwrap_or_else(|| infcx.err_ctxt().report_region_errors(external_impl, &errors)));
140    }
141
142    Ok(())
143}
144
145/// Checks a bunch of different properties of the impl/trait methods for
146/// compatibility, such as asyncness, number of argument, self receiver kind,
147/// and number of early- and late-bound generics.
148///
149/// Corresponds to `check_method_is_structurally_compatible` for impl method compatibility checks.
150fn check_is_structurally_compatible<'tcx>(
151    tcx: TyCtxt<'tcx>,
152    external_impl: LocalDefId,
153    declaration: DefId,
154    eii_name: Symbol,
155    eii_attr_span: Span,
156) -> Result<(), ErrorGuaranteed> {
157    check_no_generics(tcx, external_impl, declaration, eii_name, eii_attr_span)?;
158    check_number_of_arguments(tcx, external_impl, declaration, eii_name, eii_attr_span)?;
159    check_early_region_bounds(tcx, external_impl, declaration, eii_attr_span)?;
160    Ok(())
161}
162
163/// externally implementable items can't have generics
164fn check_no_generics<'tcx>(
165    tcx: TyCtxt<'tcx>,
166    external_impl: LocalDefId,
167    _declaration: DefId,
168    eii_name: Symbol,
169    eii_attr_span: Span,
170) -> Result<(), ErrorGuaranteed> {
171    let generics = tcx.generics_of(external_impl);
172    if generics.own_requires_monomorphization() {
173        tcx.dcx().emit_err(EiiWithGenerics {
174            span: tcx.def_span(external_impl),
175            attr: eii_attr_span,
176            eii_name,
177        });
178    }
179
180    Ok(())
181}
182
183fn check_early_region_bounds<'tcx>(
184    tcx: TyCtxt<'tcx>,
185    external_impl: LocalDefId,
186    declaration: DefId,
187    eii_attr_span: Span,
188) -> Result<(), ErrorGuaranteed> {
189    let external_impl_generics = tcx.generics_of(external_impl.to_def_id());
190    let external_impl_params = external_impl_generics.own_counts().lifetimes;
191
192    let declaration_generics = tcx.generics_of(declaration);
193    let declaration_params = declaration_generics.own_counts().lifetimes;
194
195    let Err(CheckNumberOfEarlyBoundRegionsError { span, generics_span, bounds_span, where_span }) =
196        check_number_of_early_bound_regions(
197            tcx,
198            external_impl,
199            declaration,
200            external_impl_generics,
201            external_impl_params,
202            declaration_generics,
203            declaration_params,
204        )
205    else {
206        return Ok(());
207    };
208
209    let mut diag = tcx.dcx().create_err(LifetimesOrBoundsMismatchOnEii {
210        span,
211        ident: tcx.item_name(external_impl.to_def_id()),
212        generics_span,
213        bounds_span,
214        where_span,
215    });
216
217    diag.span_label(eii_attr_span, format!("required because of this attribute"));
218    return Err(diag.emit());
219}
220
221fn check_number_of_arguments<'tcx>(
222    tcx: TyCtxt<'tcx>,
223    external_impl: LocalDefId,
224    declaration: DefId,
225    eii_name: Symbol,
226    eii_attr_span: Span,
227) -> Result<(), ErrorGuaranteed> {
228    let external_impl_fty = tcx.fn_sig(external_impl);
229    let declaration_fty = tcx.fn_sig(declaration);
230    let declaration_number_args = declaration_fty.skip_binder().inputs().skip_binder().len();
231    let external_impl_number_args = external_impl_fty.skip_binder().inputs().skip_binder().len();
232
233    // if the number of args are equal, we're trivially done
234    if declaration_number_args == external_impl_number_args {
235        Ok(())
236    } else {
237        Err(report_number_of_arguments_mismatch(
238            tcx,
239            external_impl,
240            declaration,
241            eii_name,
242            eii_attr_span,
243            declaration_number_args,
244            external_impl_number_args,
245        ))
246    }
247}
248
249fn report_number_of_arguments_mismatch<'tcx>(
250    tcx: TyCtxt<'tcx>,
251    external_impl: LocalDefId,
252    declaration: DefId,
253    eii_name: Symbol,
254    eii_attr_span: Span,
255    declaration_number_args: usize,
256    external_impl_number_args: usize,
257) -> ErrorGuaranteed {
258    let external_impl_name = tcx.item_name(external_impl.to_def_id());
259
260    let declaration_span = declaration
261        .as_local()
262        .and_then(|def_id| {
263            let declaration_sig = get_declaration_sig(tcx, def_id).expect("foreign item sig");
264            let pos = declaration_number_args.saturating_sub(1);
265            declaration_sig.decl.inputs.get(pos).map(|arg| {
266                if pos == 0 {
267                    arg.span
268                } else {
269                    arg.span.with_lo(declaration_sig.decl.inputs[0].span.lo())
270                }
271            })
272        })
273        .or_else(|| tcx.hir_span_if_local(declaration))
274        .unwrap_or_else(|| tcx.def_span(declaration));
275
276    let (_, external_impl_sig, _, _) = &tcx.hir_expect_item(external_impl).expect_fn();
277    let pos = external_impl_number_args.saturating_sub(1);
278    let impl_span = external_impl_sig
279        .decl
280        .inputs
281        .get(pos)
282        .map(|arg| {
283            if pos == 0 {
284                arg.span
285            } else {
286                arg.span.with_lo(external_impl_sig.decl.inputs[0].span.lo())
287            }
288        })
289        .unwrap_or_else(|| tcx.def_span(external_impl));
290
291    let mut err = struct_span_code_err!(
292        tcx.dcx(),
293        impl_span,
294        E0806,
295        "`{external_impl_name}` has {} but #[{eii_name}] requires it to have {}",
296        potentially_plural_count(external_impl_number_args, "parameter"),
297        declaration_number_args
298    );
299
300    err.span_label(
301        declaration_span,
302        format!("requires {}", potentially_plural_count(declaration_number_args, "parameter")),
303    );
304
305    err.span_label(
306        impl_span,
307        format!(
308            "expected {}, found {}",
309            potentially_plural_count(declaration_number_args, "parameter"),
310            external_impl_number_args
311        ),
312    );
313
314    err.span_label(eii_attr_span, format!("required because of this attribute"));
315
316    err.emit()
317}
318
319fn report_eii_mismatch<'tcx>(
320    infcx: &InferCtxt<'tcx>,
321    mut cause: ObligationCause<'tcx>,
322    param_env: ty::ParamEnv<'tcx>,
323    terr: TypeError<'tcx>,
324    (declaration_did, declaration_sig): (DefId, ty::FnSig<'tcx>),
325    (external_impl_did, external_impl_sig): (LocalDefId, ty::FnSig<'tcx>),
326    eii_attr_span: Span,
327    eii_name: Symbol,
328) -> ErrorGuaranteed {
329    let tcx = infcx.tcx;
330    let (impl_err_span, trait_err_span, external_impl_name) =
331        extract_spans_for_error_reporting(infcx, terr, &cause, declaration_did, external_impl_did);
332
333    let mut diag = struct_span_code_err!(
334        tcx.dcx(),
335        impl_err_span,
336        E0806,
337        "function `{}` has a type that is incompatible with the declaration of `#[{eii_name}]`",
338        external_impl_name
339    );
340
341    diag.span_note(eii_attr_span, "expected this because of this attribute");
342
343    match &terr {
344        TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(_, i) => {
345            if declaration_sig.inputs().len() == *i {
346                // Suggestion to change output type. We do not suggest in `async` functions
347                // to avoid complex logic or incorrect output.
348                if let ItemKind::Fn { sig, .. } = &tcx.hir_expect_item(external_impl_did).kind
349                    && !sig.header.asyncness.is_async()
350                {
351                    let msg = "change the output type to match the declaration";
352                    let ap = Applicability::MachineApplicable;
353                    match sig.decl.output {
354                        hir::FnRetTy::DefaultReturn(sp) => {
355                            let sugg = format!(" -> {}", declaration_sig.output());
356                            diag.span_suggestion_verbose(sp, msg, sugg, ap);
357                        }
358                        hir::FnRetTy::Return(hir_ty) => {
359                            let sugg = declaration_sig.output();
360                            diag.span_suggestion_verbose(hir_ty.span, msg, sugg, ap);
361                        }
362                    };
363                };
364            } else if let Some(trait_ty) = declaration_sig.inputs().get(*i) {
365                diag.span_suggestion_verbose(
366                    impl_err_span,
367                    "change the parameter type to match the declaration",
368                    trait_ty,
369                    Applicability::MachineApplicable,
370                );
371            }
372        }
373        _ => {}
374    }
375
376    cause.span = impl_err_span;
377    infcx.err_ctxt().note_type_err(
378        &mut diag,
379        &cause,
380        trait_err_span.map(|sp| (sp, Cow::from("type in declaration"), false)),
381        Some(param_env.and(infer::ValuePairs::PolySigs(ExpectedFound {
382            expected: ty::Binder::dummy(declaration_sig),
383            found: ty::Binder::dummy(external_impl_sig),
384        }))),
385        terr,
386        false,
387        None,
388    );
389
390    diag.emit()
391}
392
393#[instrument(level = "debug", skip(infcx))]
394fn extract_spans_for_error_reporting<'tcx>(
395    infcx: &infer::InferCtxt<'tcx>,
396    terr: TypeError<'_>,
397    cause: &ObligationCause<'tcx>,
398    declaration: DefId,
399    external_impl: LocalDefId,
400) -> (Span, Option<Span>, Ident) {
401    let tcx = infcx.tcx;
402    let (mut external_impl_args, external_impl_name) = {
403        let item = tcx.hir_expect_item(external_impl);
404        let (ident, sig, _, _) = item.expect_fn();
405        (sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())), ident)
406    };
407
408    let declaration_args = declaration.as_local().map(|def_id| {
409        if let Some(sig) = get_declaration_sig(tcx, def_id) {
410            sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span()))
411        } else {
412            panic!("expected {def_id:?} to be a foreign function");
413        }
414    });
415
416    match terr {
417        TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => (
418            external_impl_args.nth(i).unwrap(),
419            declaration_args.and_then(|mut args| args.nth(i)),
420            external_impl_name,
421        ),
422        _ => (
423            cause.span,
424            tcx.hir_span_if_local(declaration).or_else(|| Some(tcx.def_span(declaration))),
425            external_impl_name,
426        ),
427    }
428}
429
430fn get_declaration_sig<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Option<&'tcx FnSig<'tcx>> {
431    let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id);
432    tcx.hir_fn_sig_by_hir_id(hir_id)
433}