diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index bcf4d7f59e34..8022a48ee137 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -165,6 +165,10 @@ hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported hir_analysis_duplicate_precise_capture = cannot capture parameter `{$name}` twice .label = parameter captured again here +hir_analysis_eii_with_generics = + #[{$eii_name}] cannot have generic parameters other than lifetimes + .label = required by this attribute + hir_analysis_empty_specialization = specialization impl does not specialize any associated items .note = impl is a specialization of this impl @@ -300,6 +304,13 @@ hir_analysis_lifetime_not_captured = `impl Trait` captures lifetime parameter, b .label = lifetime captured due to being mentioned in the bounds of the `impl Trait` .param_label = this lifetime parameter is captured +hir_analysis_lifetimes_or_bounds_mismatch_on_eii = + lifetime parameters or bounds of `{$ident}` do not match the declaration + .label = lifetimes do not match + .generics_label = lifetimes in impl do not match this signature + .where_label = this `where` clause might not match the one in the declaration + .bounds_label = this bound might be missing in the implementation + hir_analysis_lifetimes_or_bounds_mismatch_on_trait = lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration .label = lifetimes do not match {$item_kind} in trait diff --git a/compiler/rustc_hir_analysis/src/check/compare_eii.rs b/compiler/rustc_hir_analysis/src/check/compare_eii.rs new file mode 100644 index 000000000000..06b7f4952957 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/check/compare_eii.rs @@ -0,0 +1,424 @@ +use std::borrow::Cow; +use std::iter; + +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Applicability, E0805, struct_span_code_err}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, FnSig, HirId, ItemKind}; +use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt}; +use rustc_infer::traits::{ObligationCause, ObligationCauseCode}; +use rustc_middle::ty::error::{ExpectedFound, TypeError}; +use rustc_middle::ty::{self, TyCtxt, TypingMode}; +use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::regions::InferCtxtRegionExt; +use rustc_trait_selection::traits::ObligationCtxt; +use tracing::{debug, instrument}; + +use super::potentially_plural_count; +use crate::errors::{EiiWithGenerics, LifetimesOrBoundsMismatchOnEii}; + +/// checks whether the signature of some `external_impl`, matches +/// the signature of `declaration`, which it is supposed to be compatible +/// with in order to implement the item. +pub(crate) fn compare_eii_function_types<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + check_is_structurally_compatible(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + + let external_impl_span = tcx.def_span(external_impl); + let cause = ObligationCause::new( + external_impl_span, + external_impl, + ObligationCauseCode::CompareEii { external_impl, declaration }, + ); + + // FIXME(eii): even if we don't support generic functions, we should support explicit outlive bounds here + let param_env = tcx.param_env(declaration); + + let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + + // We now need to check that the signature of the impl method is + // compatible with that of the trait method. We do this by + // checking that `impl_fty <: trait_fty`. + // + // FIXME: We manually instantiate the trait method here as we need + // to manually compute its implied bounds. Otherwise this could just + // be ocx.sub(impl_sig, trait_sig). + + let wf_tys = FxIndexSet::default(); + let norm_cause = ObligationCause::misc(external_impl_span, external_impl); + + let declaration_sig = tcx.fn_sig(declaration).instantiate_identity(); + let declaration_sig = infcx.enter_forall_and_leak_universe(declaration_sig); + let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig); + + let external_impl_sig = infcx.instantiate_binder_with_fresh_vars( + external_impl_span, + infer::BoundRegionConversionTime::HigherRankedType, + tcx.fn_sig(external_impl).instantiate( + tcx, + infcx.fresh_args_for_item(external_impl_span, external_impl.to_def_id()), + ), + ); + let external_impl_sig = ocx.normalize(&norm_cause, param_env, external_impl_sig); + debug!(?external_impl_sig); + + // FIXME: We'd want to keep more accurate spans than "the method signature" when + // processing the comparison between the trait and impl fn, but we sadly lose them + // and point at the whole signature when a trait bound or specific input or output + // type would be more appropriate. In other places we have a `Vec` + // corresponding to their `Vec`, but we don't have that here. + // Fixing this would improve the output of test `issue-83765.rs`. + let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig); + + if let Err(terr) = result { + debug!(?external_impl_sig, ?declaration_sig, ?terr, "sub_types failed"); + + let emitted = report_eii_mismatch( + infcx, + cause, + param_env, + terr, + (declaration, declaration_sig), + (external_impl, external_impl_sig), + eii_attr_span, + eii_name, + ); + return Err(emitted); + } + + // Check that all obligations are satisfied by the implementation's + // version. + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + if !errors.is_empty() { + let reported = infcx.err_ctxt().report_fulfillment_errors(errors); + return Err(reported); + } + + // Finally, resolve all regions. This catches wily misuses of + // lifetime parameters. + let errors = infcx.resolve_regions(external_impl, param_env, wf_tys); + if !errors.is_empty() { + return Err(infcx + .tainted_by_errors() + .unwrap_or_else(|| infcx.err_ctxt().report_region_errors(external_impl, &errors))); + } + + Ok(()) +} + +/// Checks a bunch of different properties of the impl/trait methods for +/// compatibility, such as asyncness, number of argument, self receiver kind, +/// and number of early- and late-bound generics. +fn check_is_structurally_compatible<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + check_no_generics(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + compare_number_of_method_arguments(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + check_region_bounds_on_impl_item(tcx, external_impl, declaration, eii_attr_span)?; + Ok(()) +} + +fn check_no_generics<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + _declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let generics = tcx.generics_of(external_impl); + if generics.own_requires_monomorphization() { + tcx.dcx().emit_err(EiiWithGenerics { + span: tcx.def_span(external_impl), + attr: eii_attr_span, + eii_name, + }); + } + + Ok(()) +} + +fn compare_number_of_method_arguments<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let external_impl_fty = tcx.fn_sig(external_impl); + let declaration_fty = tcx.fn_sig(declaration); + let declaration_number_args = declaration_fty.skip_binder().inputs().skip_binder().len(); + let external_impl_number_args = external_impl_fty.skip_binder().inputs().skip_binder().len(); + let external_impl_name = tcx.item_name(external_impl.to_def_id()); + + if declaration_number_args != external_impl_number_args { + let declaration_span = declaration + .as_local() + .and_then(|def_id| { + let declaration_sig = get_declaration_sig(tcx, def_id).expect("foreign item sig"); + let pos = declaration_number_args.saturating_sub(1); + declaration_sig.decl.inputs.get(pos).map(|arg| { + if pos == 0 { + arg.span + } else { + arg.span.with_lo(declaration_sig.decl.inputs[0].span.lo()) + } + }) + }) + .or_else(|| tcx.hir_span_if_local(declaration)) + .unwrap_or_else(|| tcx.def_span(declaration)); + + let (_, external_impl_sig, _, _) = &tcx.hir_expect_item(external_impl).expect_fn(); + let pos = external_impl_number_args.saturating_sub(1); + let impl_span = external_impl_sig + .decl + .inputs + .get(pos) + .map(|arg| { + if pos == 0 { + arg.span + } else { + arg.span.with_lo(external_impl_sig.decl.inputs[0].span.lo()) + } + }) + .unwrap_or_else(|| tcx.def_span(external_impl)); + + let mut err = struct_span_code_err!( + tcx.dcx(), + impl_span, + E0805, + "`{external_impl_name}` has {} but #[{eii_name}] requires it to have {}", + potentially_plural_count(external_impl_number_args, "parameter"), + declaration_number_args + ); + + // if let Some(declaration_span) = declaration_span { + err.span_label( + declaration_span, + format!("requires {}", potentially_plural_count(declaration_number_args, "parameter")), + ); + // } + + err.span_label( + impl_span, + format!( + "expected {}, found {}", + potentially_plural_count(declaration_number_args, "parameter"), + external_impl_number_args + ), + ); + + err.span_label(eii_attr_span, format!("required because of this attribute")); + + return Err(err.emit()); + } + + Ok(()) +} + +fn check_region_bounds_on_impl_item<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let external_impl_generics = tcx.generics_of(external_impl.to_def_id()); + let external_impl_params = external_impl_generics.own_counts().lifetimes; + + let declaration_generics = tcx.generics_of(declaration); + let declaration_params = declaration_generics.own_counts().lifetimes; + + debug!(?declaration_generics, ?external_impl_generics); + + // Must have same number of early-bound lifetime parameters. + // Unfortunately, if the user screws up the bounds, then this + // will change classification between early and late. E.g., + // if in trait we have `<'a,'b:'a>`, and in impl we just have + // `<'a,'b>`, then we have 2 early-bound lifetime parameters + // in trait but 0 in the impl. But if we report "expected 2 + // but found 0" it's confusing, because it looks like there + // are zero. Since I don't quite know how to phrase things at + // the moment, give a kind of vague error message. + if declaration_params != external_impl_params { + let span = tcx + .hir_get_generics(external_impl) + .expect("expected impl item to have generics or else we can't compare them") + .span; + + let mut generics_span = None; + let mut bounds_span = vec![]; + let mut where_span = None; + + if let Some(declaration_node) = tcx.hir_get_if_local(declaration) + && let Some(declaration_generics) = declaration_node.generics() + { + generics_span = Some(declaration_generics.span); + // FIXME: we could potentially look at the impl's bounds to not point at bounds that + // *are* present in the impl. + for p in declaration_generics.predicates { + if let hir::WherePredicateKind::BoundPredicate(pred) = p.kind { + for b in pred.bounds { + if let hir::GenericBound::Outlives(lt) = b { + bounds_span.push(lt.ident.span); + } + } + } + } + if let Some(implementation_generics) = tcx.hir_get_generics(external_impl) { + let mut impl_bounds = 0; + for p in implementation_generics.predicates { + if let hir::WherePredicateKind::BoundPredicate(pred) = p.kind { + for b in pred.bounds { + if let hir::GenericBound::Outlives(_) = b { + impl_bounds += 1; + } + } + } + } + if impl_bounds == bounds_span.len() { + bounds_span = vec![]; + } else if implementation_generics.has_where_clause_predicates { + where_span = Some(implementation_generics.where_clause_span); + } + } + } + let mut diag = tcx.dcx().create_err(LifetimesOrBoundsMismatchOnEii { + span, + ident: tcx.item_name(external_impl.to_def_id()), + generics_span, + bounds_span, + where_span, + }); + + diag.span_label(eii_attr_span, format!("required because of this attribute")); + return Err(diag.emit()); + } + + Ok(()) +} + +fn report_eii_mismatch<'tcx>( + infcx: &InferCtxt<'tcx>, + mut cause: ObligationCause<'tcx>, + param_env: ty::ParamEnv<'tcx>, + terr: TypeError<'tcx>, + (declaration_did, declaration_sig): (DefId, ty::FnSig<'tcx>), + (external_impl_did, external_impl_sig): (LocalDefId, ty::FnSig<'tcx>), + eii_attr_span: Span, + eii_name: Symbol, +) -> ErrorGuaranteed { + let tcx = infcx.tcx; + let (impl_err_span, trait_err_span, external_impl_name) = + extract_spans_for_error_reporting(infcx, terr, &cause, declaration_did, external_impl_did); + + let mut diag = struct_span_code_err!( + tcx.dcx(), + impl_err_span, + E0805, + "function `{}` has a type that is incompatible with the declaration of `#[{eii_name}]`", + external_impl_name + ); + + diag.span_note(eii_attr_span, "expected this because of this attribute"); + + match &terr { + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(_, i) => { + if declaration_sig.inputs().len() == *i { + // Suggestion to change output type. We do not suggest in `async` functions + // to avoid complex logic or incorrect output. + if let ItemKind::Fn { sig, .. } = &tcx.hir_expect_item(external_impl_did).kind + && !sig.header.asyncness.is_async() + { + let msg = "change the output type to match the declaration"; + let ap = Applicability::MachineApplicable; + match sig.decl.output { + hir::FnRetTy::DefaultReturn(sp) => { + let sugg = format!(" -> {}", declaration_sig.output()); + diag.span_suggestion_verbose(sp, msg, sugg, ap); + } + hir::FnRetTy::Return(hir_ty) => { + let sugg = declaration_sig.output(); + diag.span_suggestion_verbose(hir_ty.span, msg, sugg, ap); + } + }; + }; + } else if let Some(trait_ty) = declaration_sig.inputs().get(*i) { + diag.span_suggestion_verbose( + impl_err_span, + "change the parameter type to match the declaration", + trait_ty, + Applicability::MachineApplicable, + ); + } + } + _ => {} + } + + cause.span = impl_err_span; + infcx.err_ctxt().note_type_err( + &mut diag, + &cause, + trait_err_span.map(|sp| (sp, Cow::from("type in declaration"), false)), + Some(param_env.and(infer::ValuePairs::PolySigs(ExpectedFound { + expected: ty::Binder::dummy(declaration_sig), + found: ty::Binder::dummy(external_impl_sig), + }))), + terr, + false, + None, + ); + + diag.emit() +} + +#[instrument(level = "debug", skip(infcx))] +fn extract_spans_for_error_reporting<'tcx>( + infcx: &infer::InferCtxt<'tcx>, + terr: TypeError<'_>, + cause: &ObligationCause<'tcx>, + declaration: DefId, + external_impl: LocalDefId, +) -> (Span, Option, Ident) { + let tcx = infcx.tcx; + let (mut external_impl_args, external_impl_name) = { + let item = tcx.hir_expect_item(external_impl); + let (ident, sig, _, _) = item.expect_fn(); + (sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())), ident) + }; + + let declaration_args = declaration.as_local().map(|def_id| { + if let Some(sig) = get_declaration_sig(tcx, def_id) { + sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())) + } else { + panic!("expected {def_id:?} to be a foreign function"); + } + }); + + match terr { + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => ( + external_impl_args.nth(i).unwrap(), + declaration_args.and_then(|mut args| args.nth(i)), + external_impl_name, + ), + _ => ( + cause.span, + tcx.hir_span_if_local(declaration).or_else(|| Some(tcx.def_span(declaration))), + external_impl_name, + ), + } +} + +fn get_declaration_sig<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Option<&'tcx FnSig<'tcx>> { + let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id); + tcx.hir_fn_sig_by_hir_id(hir_id) +} diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index f1c84a14de30..d6ae14a7acfe 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -64,6 +64,7 @@ a type parameter). pub mod always_applicable; mod check; +mod compare_eii; mod compare_impl_item; mod entry; pub mod intrinsic; diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0e8859facf58..97097f4a0bfc 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -6,7 +6,7 @@ use rustc_abi::ExternAbi; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{Applicability, ErrorGuaranteed, pluralize, struct_span_code_err}; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, EiiDecl, EiiImpl}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; @@ -39,6 +39,7 @@ use rustc_trait_selection::traits::{ use tracing::{debug, instrument}; use {rustc_ast as ast, rustc_hir as hir}; +use super::compare_eii::compare_eii_function_types; use crate::autoderef::Autoderef; use crate::constrained_generic_params::{Parameter, identify_constrained_generic_params}; use crate::errors::InvalidReceiverTyHint; @@ -1171,6 +1172,31 @@ fn check_item_fn( decl: &hir::FnDecl<'_>, ) -> Result<(), ErrorGuaranteed> { enter_wf_checking_ctxt(tcx, def_id, |wfcx| { + // does the function have an EiiImpl attribute? that contains the defid of a *macro* + // that was used to mark the implementation. This is a two step process. + for EiiImpl { eii_macro, span, .. } in + find_attr!(tcx.get_all_attrs(def_id), AttributeKind::EiiImpls(impls) => impls) + .into_iter() + .flatten() + { + // we expect this macro to have the `EiiMacroFor` attribute, that points to a function + // signature that we'd like to compare the function we're currently checking with + if let Some(eii_extern_target) = find_attr!(tcx.get_all_attrs(*eii_macro), AttributeKind::EiiExternTarget(EiiDecl {eii_extern_target, ..}) => *eii_extern_target) + { + let _ = compare_eii_function_types( + tcx, + def_id, + eii_extern_target, + tcx.item_name(*eii_macro), + *span, + ); + } else { + panic!( + "EII impl macro {eii_macro:?} did not have an eii extern target attribute pointing to a foreign function" + ) + } + } + let sig = tcx.fn_sig(def_id).instantiate_identity(); check_fn_or_method(wfcx, sig, decl, def_id); Ok(()) diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 34ad4e720eb5..865abdbd32c2 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1697,3 +1697,28 @@ pub(crate) struct AsyncDropWithoutSyncDrop { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(hir_analysis_lifetimes_or_bounds_mismatch_on_eii)] +pub(crate) struct LifetimesOrBoundsMismatchOnEii { + #[primary_span] + #[label] + pub span: Span, + #[label(hir_analysis_generics_label)] + pub generics_span: Option, + #[label(hir_analysis_where_label)] + pub where_span: Option, + #[label(hir_analysis_bounds_label)] + pub bounds_span: Vec, + pub ident: Symbol, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_eii_with_generics)] +pub(crate) struct EiiWithGenerics { + #[primary_span] + pub span: Span, + #[label] + pub attr: Span, + pub eii_name: Symbol, +} diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 0c7bddf60d97..c064313f67b2 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -420,6 +420,13 @@ pub enum ObligationCauseCode<'tcx> { /// Only reachable if the `unsized_fn_params` feature is used. Unsized function arguments must /// be place expressions because we can't store them in MIR locals as temporaries. UnsizedNonPlaceExpr(Span), + + /// Error derived when checking an impl item is compatible with + /// its corresponding trait item's definition + CompareEii { + external_impl: LocalDefId, + declaration: DefId, + }, } /// Whether a value can be extracted into a const. diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index fd845e31c909..d14ff2c3b7e2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3843,6 +3843,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { "unsized values must be place expressions and cannot be put in temporaries", ); } + ObligationCauseCode::CompareEii { .. } => { + panic!("trait bounds on EII not yet supported ") + } } }