|
| 1 | +use std::borrow::Cow; |
| 2 | +use std::iter; |
| 3 | + |
| 4 | +use rustc_data_structures::fx::FxIndexSet; |
| 5 | +use rustc_errors::{Applicability, E0053, struct_span_code_err}; |
| 6 | +use rustc_hir::def_id::{DefId, LocalDefId}; |
| 7 | +use rustc_hir::{self as hir, HirId, ItemKind}; |
| 8 | +use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt}; |
| 9 | +use rustc_infer::traits::{ObligationCause, ObligationCauseCode}; |
| 10 | +use rustc_middle::ty; |
| 11 | +use rustc_middle::ty::TyCtxt; |
| 12 | +use rustc_middle::ty::error::{ExpectedFound, TypeError}; |
| 13 | +use rustc_span::{ErrorGuaranteed, Ident, Span}; |
| 14 | +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; |
| 15 | +use rustc_trait_selection::regions::InferCtxtRegionExt; |
| 16 | +use rustc_trait_selection::traits::ObligationCtxt; |
| 17 | +use rustc_type_ir::TypingMode; |
| 18 | +use tracing::{debug, instrument}; |
| 19 | + |
| 20 | +// checks whether the signature of some `external_impl`, matches |
| 21 | +// the signature of `declaration`, which it is supposed to be compatible |
| 22 | +// with in order to implement the item. |
| 23 | +pub(crate) fn compare_eii_predicate_entailment<'tcx>( |
| 24 | + tcx: TyCtxt<'tcx>, |
| 25 | + external_impl: LocalDefId, |
| 26 | + declaration: DefId, |
| 27 | +) -> Result<(), ErrorGuaranteed> { |
| 28 | + let external_impl_span = tcx.def_span(external_impl); |
| 29 | + let cause = ObligationCause::new( |
| 30 | + external_impl_span, |
| 31 | + external_impl, |
| 32 | + ObligationCauseCode::CompareEII { external_impl, declaration }, |
| 33 | + ); |
| 34 | + |
| 35 | + // no trait bounds |
| 36 | + let param_env = ty::ParamEnv::empty(); |
| 37 | + |
| 38 | + let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis()); |
| 39 | + let ocx = ObligationCtxt::new_with_diagnostics(infcx); |
| 40 | + |
| 41 | + // We now need to check that the signature of the impl method is |
| 42 | + // compatible with that of the trait method. We do this by |
| 43 | + // checking that `impl_fty <: trait_fty`. |
| 44 | + // |
| 45 | + // FIXME. Unfortunately, this doesn't quite work right now because |
| 46 | + // associated type normalization is not integrated into subtype |
| 47 | + // checks. For the comparison to be valid, we need to |
| 48 | + // normalize the associated types in the impl/trait methods |
| 49 | + // first. However, because function types bind regions, just |
| 50 | + // calling `FnCtxt::normalize` would have no effect on |
| 51 | + // any associated types appearing in the fn arguments or return |
| 52 | + // type. |
| 53 | + |
| 54 | + let wf_tys = FxIndexSet::default(); |
| 55 | + |
| 56 | + let external_impl_sig = infcx.instantiate_binder_with_fresh_vars( |
| 57 | + external_impl_span, |
| 58 | + infer::HigherRankedType, |
| 59 | + tcx.fn_sig(external_impl).instantiate_identity(), |
| 60 | + ); |
| 61 | + |
| 62 | + let norm_cause = ObligationCause::misc(external_impl_span, external_impl); |
| 63 | + let external_impl_sig = ocx.normalize(&norm_cause, param_env, external_impl_sig); |
| 64 | + debug!(?external_impl_sig); |
| 65 | + |
| 66 | + let declaration_sig = tcx.fn_sig(declaration).no_bound_vars().expect("no bound vars"); |
| 67 | + let declaration_sig = |
| 68 | + tcx.liberate_late_bound_regions(external_impl.to_def_id(), declaration_sig); |
| 69 | + let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig); |
| 70 | + |
| 71 | + // FIXME: We'd want to keep more accurate spans than "the method signature" when |
| 72 | + // processing the comparison between the trait and impl fn, but we sadly lose them |
| 73 | + // and point at the whole signature when a trait bound or specific input or output |
| 74 | + // type would be more appropriate. In other places we have a `Vec<Span>` |
| 75 | + // corresponding to their `Vec<Predicate>`, but we don't have that here. |
| 76 | + // Fixing this would improve the output of test `issue-83765.rs`. |
| 77 | + let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig); |
| 78 | + |
| 79 | + if let Err(terr) = result { |
| 80 | + debug!(?external_impl_sig, ?declaration_sig, ?terr, "sub_types failed"); |
| 81 | + |
| 82 | + // TODO: nice error |
| 83 | + let emitted = report_eii_mismatch( |
| 84 | + infcx, |
| 85 | + cause, |
| 86 | + param_env, |
| 87 | + terr, |
| 88 | + (declaration, declaration_sig), |
| 89 | + (external_impl, external_impl_sig), |
| 90 | + ); |
| 91 | + return Err(emitted); |
| 92 | + } |
| 93 | + |
| 94 | + // Check that all obligations are satisfied by the implementation's |
| 95 | + // version. |
| 96 | + let errors = ocx.select_all_or_error(); |
| 97 | + if !errors.is_empty() { |
| 98 | + let reported = infcx.err_ctxt().report_fulfillment_errors(errors); |
| 99 | + return Err(reported); |
| 100 | + } |
| 101 | + |
| 102 | + // Finally, resolve all regions. This catches wily misuses of |
| 103 | + // lifetime parameters. |
| 104 | + let errors = infcx.resolve_regions(external_impl, param_env, wf_tys); |
| 105 | + if !errors.is_empty() { |
| 106 | + return Err(infcx |
| 107 | + .tainted_by_errors() |
| 108 | + .unwrap_or_else(|| infcx.err_ctxt().report_region_errors(external_impl, &errors))); |
| 109 | + } |
| 110 | + |
| 111 | + Ok(()) |
| 112 | +} |
| 113 | + |
| 114 | +fn report_eii_mismatch<'tcx>( |
| 115 | + infcx: &InferCtxt<'tcx>, |
| 116 | + mut cause: ObligationCause<'tcx>, |
| 117 | + param_env: ty::ParamEnv<'tcx>, |
| 118 | + terr: TypeError<'tcx>, |
| 119 | + (declaration_did, declaration_sig): (DefId, ty::FnSig<'tcx>), |
| 120 | + (external_impl_did, external_impl_sig): (LocalDefId, ty::FnSig<'tcx>), |
| 121 | +) -> ErrorGuaranteed { |
| 122 | + let tcx = infcx.tcx; |
| 123 | + let (impl_err_span, trait_err_span, external_impl_name) = |
| 124 | + extract_spans_for_error_reporting(infcx, terr, &cause, declaration_did, external_impl_did); |
| 125 | + |
| 126 | + let mut diag = struct_span_code_err!( |
| 127 | + tcx.dcx(), |
| 128 | + impl_err_span, |
| 129 | + E0053, // TODO: new error code |
| 130 | + "function `{}` has a type that is incompatible with the declaration", |
| 131 | + external_impl_name |
| 132 | + ); |
| 133 | + match &terr { |
| 134 | + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(_, i) => { |
| 135 | + if declaration_sig.inputs().len() == *i { |
| 136 | + // Suggestion to change output type. We do not suggest in `async` functions |
| 137 | + // to avoid complex logic or incorrect output. |
| 138 | + if let ItemKind::Fn { sig, .. } = &tcx.hir().expect_item(external_impl_did).kind |
| 139 | + && !sig.header.asyncness.is_async() |
| 140 | + { |
| 141 | + let msg = "change the output type to match the declaration"; |
| 142 | + let ap = Applicability::MachineApplicable; |
| 143 | + match sig.decl.output { |
| 144 | + hir::FnRetTy::DefaultReturn(sp) => { |
| 145 | + let sugg = format!(" -> {}", declaration_sig.output()); |
| 146 | + diag.span_suggestion_verbose(sp, msg, sugg, ap); |
| 147 | + } |
| 148 | + hir::FnRetTy::Return(hir_ty) => { |
| 149 | + let sugg = declaration_sig.output(); |
| 150 | + diag.span_suggestion_verbose(hir_ty.span, msg, sugg, ap); |
| 151 | + } |
| 152 | + }; |
| 153 | + }; |
| 154 | + } else if let Some(trait_ty) = declaration_sig.inputs().get(*i) { |
| 155 | + diag.span_suggestion_verbose( |
| 156 | + impl_err_span, |
| 157 | + "change the parameter type to match the declaration", |
| 158 | + trait_ty, |
| 159 | + Applicability::MachineApplicable, |
| 160 | + ); |
| 161 | + } |
| 162 | + } |
| 163 | + _ => {} |
| 164 | + } |
| 165 | + |
| 166 | + cause.span = impl_err_span; |
| 167 | + infcx.err_ctxt().note_type_err( |
| 168 | + &mut diag, |
| 169 | + &cause, |
| 170 | + trait_err_span.map(|sp| (sp, Cow::from("type in declaration"), false)), |
| 171 | + Some(param_env.and(infer::ValuePairs::PolySigs(ExpectedFound { |
| 172 | + expected: ty::Binder::dummy(declaration_sig), |
| 173 | + found: ty::Binder::dummy(external_impl_sig), |
| 174 | + }))), |
| 175 | + terr, |
| 176 | + false, |
| 177 | + None, |
| 178 | + ); |
| 179 | + |
| 180 | + diag.emit() |
| 181 | +} |
| 182 | + |
| 183 | +#[instrument(level = "debug", skip(infcx))] |
| 184 | +fn extract_spans_for_error_reporting<'tcx>( |
| 185 | + infcx: &infer::InferCtxt<'tcx>, |
| 186 | + terr: TypeError<'_>, |
| 187 | + cause: &ObligationCause<'tcx>, |
| 188 | + declaration: DefId, |
| 189 | + external_impl: LocalDefId, |
| 190 | +) -> (Span, Option<Span>, Ident) { |
| 191 | + let tcx = infcx.tcx; |
| 192 | + let (mut external_impl_args, external_impl_name) = { |
| 193 | + let item = tcx.hir().expect_item(external_impl); |
| 194 | + let (sig, _, _) = item.expect_fn(); |
| 195 | + ( |
| 196 | + sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())), |
| 197 | + item.ident, |
| 198 | + ) |
| 199 | + }; |
| 200 | + |
| 201 | + let declaration_args = declaration.as_local().map(|def_id| { |
| 202 | + let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id); |
| 203 | + if let Some(sig) = tcx.hir_fn_sig_by_hir_id(hir_id) { |
| 204 | + sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())) |
| 205 | + } else { |
| 206 | + panic!("expected {def_id:?} to be a foreign function"); |
| 207 | + } |
| 208 | + }); |
| 209 | + |
| 210 | + match terr { |
| 211 | + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => ( |
| 212 | + external_impl_args.nth(i).unwrap(), |
| 213 | + declaration_args.and_then(|mut args| args.nth(i)), |
| 214 | + external_impl_name, |
| 215 | + ), |
| 216 | + _ => (cause.span, tcx.hir().span_if_local(declaration), external_impl_name), |
| 217 | + } |
| 218 | +} |
0 commit comments