EII type checking

This commit is contained in:
Jana Dönszelmann 2025-08-12 14:40:05 +02:00
parent b732030025
commit 8dfb2cdece
No known key found for this signature in database
7 changed files with 498 additions and 1 deletions

View file

@ -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

View file

@ -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<Span>`
// corresponding to their `Vec<Predicate>`, 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<Span>, 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)
}

View file

@ -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;

View file

@ -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(())

View file

@ -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<Span>,
#[label(hir_analysis_where_label)]
pub where_span: Option<Span>,
#[label(hir_analysis_bounds_label)]
pub bounds_span: Vec<Span>,
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,
}

View file

@ -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.

View file

@ -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 ")
}
}
}