Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/method_resolution/confirm.rs')
| -rw-r--r-- | crates/hir-ty/src/method_resolution/confirm.rs | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/crates/hir-ty/src/method_resolution/confirm.rs b/crates/hir-ty/src/method_resolution/confirm.rs new file mode 100644 index 0000000000..9e8791edde --- /dev/null +++ b/crates/hir-ty/src/method_resolution/confirm.rs @@ -0,0 +1,616 @@ +//! Confirmation step of method selection, meaning ensuring the selected candidate +//! is valid and registering all obligations. + +use hir_def::{ + FunctionId, GenericDefId, GenericParamId, ItemContainerId, TraitId, + expr_store::path::{GenericArg as HirGenericArg, GenericArgs as HirGenericArgs}, + hir::{ExprId, generics::GenericParamDataRef}, + lang_item::LangItem, +}; +use rustc_type_ir::{ + TypeFoldable, + elaborate::elaborate, + inherent::{BoundExistentialPredicates, IntoKind, SliceLike, Ty as _}, +}; +use tracing::debug; + +use crate::{ + Adjust, Adjustment, AutoBorrow, IncorrectGenericsLenKind, InferenceDiagnostic, + LifetimeElisionKind, PointerCast, + db::HirDatabase, + infer::{AllowTwoPhase, AutoBorrowMutability, InferenceContext, TypeMismatch}, + lower::{ + GenericPredicates, + path::{GenericArgsLowerer, TypeLikeConst, substs_from_args_and_bindings}, + }, + method_resolution::{CandidateId, MethodCallee, probe}, + next_solver::{ + Binder, Clause, ClauseKind, Const, DbInterner, EarlyParamRegion, ErrorGuaranteed, FnSig, + GenericArg, GenericArgs, ParamConst, PolyExistentialTraitRef, PolyTraitRef, Region, + TraitRef, Ty, TyKind, + infer::{ + BoundRegionConversionTime, InferCtxt, + traits::{ObligationCause, PredicateObligation}, + }, + util::{clauses_as_obligations, upcast_choices}, + }, +}; + +struct ConfirmContext<'a, 'b, 'db> { + ctx: &'a mut InferenceContext<'b, 'db>, + candidate: FunctionId, + expr: ExprId, +} + +#[derive(Debug)] +pub(crate) struct ConfirmResult<'db> { + pub(crate) callee: MethodCallee<'db>, + pub(crate) illegal_sized_bound: bool, + pub(crate) adjustments: Box<[Adjustment<'db>]>, +} + +impl<'a, 'db> InferenceContext<'a, 'db> { + pub(crate) fn confirm_method( + &mut self, + pick: &probe::Pick<'db>, + unadjusted_self_ty: Ty<'db>, + expr: ExprId, + generic_args: Option<&HirGenericArgs>, + ) -> ConfirmResult<'db> { + debug!( + "confirm(unadjusted_self_ty={:?}, pick={:?}, generic_args={:?})", + unadjusted_self_ty, pick, generic_args, + ); + + let CandidateId::FunctionId(candidate) = pick.item else { + panic!("confirmation is only done for method calls, not path lookups"); + }; + let mut confirm_cx = ConfirmContext::new(self, candidate, expr); + confirm_cx.confirm(unadjusted_self_ty, pick, generic_args) + } +} + +impl<'a, 'b, 'db> ConfirmContext<'a, 'b, 'db> { + fn new( + ctx: &'a mut InferenceContext<'b, 'db>, + candidate: FunctionId, + expr: ExprId, + ) -> ConfirmContext<'a, 'b, 'db> { + ConfirmContext { ctx, candidate, expr } + } + + #[inline] + fn db(&self) -> &'db dyn HirDatabase { + self.ctx.table.infer_ctxt.interner.db + } + + #[inline] + fn interner(&self) -> DbInterner<'db> { + self.ctx.table.infer_ctxt.interner + } + + #[inline] + fn infcx(&self) -> &InferCtxt<'db> { + &self.ctx.table.infer_ctxt + } + + fn confirm( + &mut self, + unadjusted_self_ty: Ty<'db>, + pick: &probe::Pick<'db>, + generic_args: Option<&HirGenericArgs>, + ) -> ConfirmResult<'db> { + // Adjust the self expression the user provided and obtain the adjusted type. + let (self_ty, adjustments) = self.adjust_self_ty(unadjusted_self_ty, pick); + + // Create generic args for the method's type parameters. + let rcvr_args = self.fresh_receiver_args(self_ty, pick); + let all_args = self.instantiate_method_args(generic_args, rcvr_args); + + debug!("rcvr_args={rcvr_args:?}, all_args={all_args:?}"); + + // Create the final signature for the method, replacing late-bound regions. + let (method_sig, method_predicates) = + self.instantiate_method_sig(pick, all_args.as_slice()); + + // If there is a `Self: Sized` bound and `Self` is a trait object, it is possible that + // something which derefs to `Self` actually implements the trait and the caller + // wanted to make a static dispatch on it but forgot to import the trait. + // See test `tests/ui/issues/issue-35976.rs`. + // + // In that case, we'll error anyway, but we'll also re-run the search with all traits + // in scope, and if we find another method which can be used, we'll output an + // appropriate hint suggesting to import the trait. + let filler_args = GenericArgs::fill_rest( + self.interner(), + self.candidate.into(), + rcvr_args, + |index, id, _| match id { + GenericParamId::TypeParamId(id) => Ty::new_param(self.interner(), id, index).into(), + GenericParamId::ConstParamId(id) => { + Const::new_param(self.interner(), ParamConst { id, index }).into() + } + GenericParamId::LifetimeParamId(id) => { + Region::new_early_param(self.interner(), EarlyParamRegion { id, index }).into() + } + }, + ); + let illegal_sized_bound = self.predicates_require_illegal_sized_bound( + GenericPredicates::query_all(self.db(), self.candidate.into()) + .iter_instantiated_copied(self.interner(), filler_args.as_slice()), + ); + + // Unify the (adjusted) self type with what the method expects. + // + // SUBTLE: if we want good error messages, because of "guessing" while matching + // traits, no trait system method can be called before this point because they + // could alter our Self-type, except for normalizing the receiver from the + // signature (which is also done during probing). + let method_sig_rcvr = method_sig.inputs().as_slice()[0]; + debug!( + "confirm: self_ty={:?} method_sig_rcvr={:?} method_sig={:?}", + self_ty, method_sig_rcvr, method_sig + ); + self.unify_receivers(self_ty, method_sig_rcvr, pick); + + // Make sure nobody calls `drop()` explicitly. + self.check_for_illegal_method_calls(); + + // Lint when an item is shadowing a supertrait item. + self.lint_shadowed_supertrait_items(pick); + + // Add any trait/regions obligations specified on the method's type parameters. + // We won't add these if we encountered an illegal sized bound, so that we can use + // a custom error in that case. + if !illegal_sized_bound { + self.add_obligations(method_sig, all_args, method_predicates); + } + + // Create the final `MethodCallee`. + let callee = MethodCallee { def_id: self.candidate, args: all_args, sig: method_sig }; + ConfirmResult { callee, illegal_sized_bound, adjustments } + } + + /////////////////////////////////////////////////////////////////////////// + // ADJUSTMENTS + + fn adjust_self_ty( + &mut self, + unadjusted_self_ty: Ty<'db>, + pick: &probe::Pick<'db>, + ) -> (Ty<'db>, Box<[Adjustment<'db>]>) { + // Commit the autoderefs by calling `autoderef` again, but this + // time writing the results into the various typeck results. + let mut autoderef = self.ctx.table.autoderef_with_tracking(unadjusted_self_ty); + let Some((mut target, n)) = autoderef.nth(pick.autoderefs) else { + return (Ty::new_error(self.interner(), ErrorGuaranteed), Box::new([])); + }; + assert_eq!(n, pick.autoderefs); + + let mut adjustments = + self.ctx.table.register_infer_ok(autoderef.adjust_steps_as_infer_ok()); + match pick.autoref_or_ptr_adjustment { + Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, unsize }) => { + let region = self.infcx().next_region_var(); + // Type we're wrapping in a reference, used later for unsizing + let base_ty = target; + + target = Ty::new_ref(self.interner(), region, target, mutbl); + + // Method call receivers are the primary use case + // for two-phase borrows. + let mutbl = AutoBorrowMutability::new(mutbl, AllowTwoPhase::Yes); + + adjustments + .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target }); + + if unsize { + let unsized_ty = if let TyKind::Array(elem_ty, _) = base_ty.kind() { + Ty::new_slice(self.interner(), elem_ty) + } else { + panic!( + "AutorefOrPtrAdjustment's unsize flag should only be set for array ty, found {:?}", + base_ty + ) + }; + target = Ty::new_ref(self.interner(), region, unsized_ty, mutbl.into()); + adjustments + .push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target }); + } + } + Some(probe::AutorefOrPtrAdjustment::ToConstPtr) => { + target = match target.kind() { + TyKind::RawPtr(ty, mutbl) => { + assert!(mutbl.is_mut()); + Ty::new_imm_ptr(self.interner(), ty) + } + other => panic!("Cannot adjust receiver type {other:?} to const ptr"), + }; + + adjustments.push(Adjustment { + kind: Adjust::Pointer(PointerCast::MutToConstPointer), + target, + }); + } + None => {} + } + + (target, adjustments.into_boxed_slice()) + } + + /// Returns a set of generic parameters for the method *receiver* where all type and region + /// parameters are instantiated with fresh variables. This generic parameters does not include any + /// parameters declared on the method itself. + /// + /// Note that this generic parameters may include late-bound regions from the impl level. If so, + /// these are instantiated later in the `instantiate_method_sig` routine. + fn fresh_receiver_args( + &mut self, + self_ty: Ty<'db>, + pick: &probe::Pick<'db>, + ) -> GenericArgs<'db> { + match pick.kind { + probe::InherentImplPick(impl_def_id) => { + self.infcx().fresh_args_for_item(impl_def_id.into()) + } + + probe::ObjectPick(trait_def_id) => { + // If the trait is not object safe (specifically, we care about when + // the receiver is not valid), then there's a chance that we will not + // actually be able to recover the object by derefing the receiver like + // we should if it were valid. + if self.db().dyn_compatibility_of_trait(trait_def_id).is_some() { + return GenericArgs::error_for_item(self.interner(), trait_def_id.into()); + } + + self.extract_existential_trait_ref(self_ty, |this, object_ty, principal| { + // The object data has no entry for the Self + // Type. For the purposes of this method call, we + // instantiate the object type itself. This + // wouldn't be a sound instantiation in all cases, + // since each instance of the object type is a + // different existential and hence could match + // distinct types (e.g., if `Self` appeared as an + // argument type), but those cases have already + // been ruled out when we deemed the trait to be + // "dyn-compatible". + let original_poly_trait_ref = + principal.with_self_ty(this.interner(), object_ty); + let upcast_poly_trait_ref = this.upcast(original_poly_trait_ref, trait_def_id); + let upcast_trait_ref = + this.instantiate_binder_with_fresh_vars(upcast_poly_trait_ref); + debug!( + "original_poly_trait_ref={:?} upcast_trait_ref={:?} target_trait={:?}", + original_poly_trait_ref, upcast_trait_ref, trait_def_id + ); + upcast_trait_ref.args + }) + } + + probe::TraitPick(trait_def_id) => { + // Make a trait reference `$0 : Trait<$1...$n>` + // consisting entirely of type variables. Later on in + // the process we will unify the transformed-self-type + // of the method with the actual type in order to + // unify some of these variables. + self.infcx().fresh_args_for_item(trait_def_id.into()) + } + + probe::WhereClausePick(poly_trait_ref) => { + // Where clauses can have bound regions in them. We need to instantiate + // those to convert from a poly-trait-ref to a trait-ref. + self.instantiate_binder_with_fresh_vars(poly_trait_ref).args + } + } + } + + fn extract_existential_trait_ref<R, F>(&self, self_ty: Ty<'db>, mut closure: F) -> R + where + F: FnMut(&ConfirmContext<'a, 'b, 'db>, Ty<'db>, PolyExistentialTraitRef<'db>) -> R, + { + // If we specified that this is an object method, then the + // self-type ought to be something that can be dereferenced to + // yield an object-type (e.g., `&Object` or `Box<Object>` + // etc). + + let mut autoderef = self.ctx.table.autoderef(self_ty); + + // We don't need to gate this behind arbitrary self types + // per se, but it does make things a bit more gated. + if self.ctx.unstable_features.arbitrary_self_types + || self.ctx.unstable_features.arbitrary_self_types_pointers + { + autoderef = autoderef.use_receiver_trait(); + } + + autoderef + .include_raw_pointers() + .find_map(|(ty, _)| match ty.kind() { + TyKind::Dynamic(data, ..) => Some(closure( + self, + ty, + data.principal().expect("calling trait method on empty object?"), + )), + _ => None, + }) + .unwrap_or_else(|| { + panic!("self-type `{:?}` for ObjectPick never dereferenced to an object", self_ty) + }) + } + + fn instantiate_method_args( + &mut self, + generic_args: Option<&HirGenericArgs>, + parent_args: GenericArgs<'db>, + ) -> GenericArgs<'db> { + struct LowererCtx<'a, 'b, 'db> { + ctx: &'a mut InferenceContext<'b, 'db>, + expr: ExprId, + parent_args: &'a [GenericArg<'db>], + } + + impl<'db> GenericArgsLowerer<'db> for LowererCtx<'_, '_, 'db> { + fn report_len_mismatch( + &mut self, + def: GenericDefId, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + ) { + self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsLen { + expr: self.expr, + provided_count, + expected_count, + kind, + def, + }); + } + + fn report_arg_mismatch( + &mut self, + param_id: GenericParamId, + arg_idx: u32, + has_self_arg: bool, + ) { + self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsOrder { + expr: self.expr, + param_id, + arg_idx, + has_self_arg, + }); + } + + fn provided_kind( + &mut self, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + arg: &HirGenericArg, + ) -> GenericArg<'db> { + match (param, arg) { + ( + GenericParamDataRef::LifetimeParamData(_), + HirGenericArg::Lifetime(lifetime), + ) => self.ctx.make_body_lifetime(*lifetime).into(), + (GenericParamDataRef::TypeParamData(_), HirGenericArg::Type(type_ref)) => { + self.ctx.make_body_ty(*type_ref).into() + } + (GenericParamDataRef::ConstParamData(_), HirGenericArg::Const(konst)) => { + let GenericParamId::ConstParamId(const_id) = param_id else { + unreachable!("non-const param ID for const param"); + }; + let const_ty = self.ctx.db.const_param_ty_ns(const_id); + self.ctx.make_body_const(*konst, const_ty).into() + } + _ => unreachable!("unmatching param kinds were passed to `provided_kind()`"), + } + } + + fn provided_type_like_const( + &mut self, + const_ty: Ty<'db>, + arg: TypeLikeConst<'_>, + ) -> Const<'db> { + match arg { + TypeLikeConst::Path(path) => self.ctx.make_path_as_body_const(path, const_ty), + TypeLikeConst::Infer => self.ctx.table.next_const_var(), + } + } + + fn inferred_kind( + &mut self, + _def: GenericDefId, + param_id: GenericParamId, + _param: GenericParamDataRef<'_>, + _infer_args: bool, + _preceding_args: &[GenericArg<'db>], + ) -> GenericArg<'db> { + // Always create an inference var, even when `infer_args == false`. This helps with diagnostics, + // and I think it's also required in the presence of `impl Trait` (that must be inferred). + self.ctx.table.next_var_for_param(param_id) + } + + fn parent_arg(&mut self, param_idx: u32, _param_id: GenericParamId) -> GenericArg<'db> { + self.parent_args[param_idx as usize] + } + + fn report_elided_lifetimes_in_path( + &mut self, + _def: GenericDefId, + _expected_count: u32, + _hard_error: bool, + ) { + unreachable!("we set `LifetimeElisionKind::Infer`") + } + + fn report_elision_failure(&mut self, _def: GenericDefId, _expected_count: u32) { + unreachable!("we set `LifetimeElisionKind::Infer`") + } + + fn report_missing_lifetime(&mut self, _def: GenericDefId, _expected_count: u32) { + unreachable!("we set `LifetimeElisionKind::Infer`") + } + } + + substs_from_args_and_bindings( + self.db(), + self.ctx.body, + generic_args, + self.candidate.into(), + true, + LifetimeElisionKind::Infer, + false, + None, + &mut LowererCtx { ctx: self.ctx, expr: self.expr, parent_args: parent_args.as_slice() }, + ) + } + + fn unify_receivers( + &mut self, + self_ty: Ty<'db>, + method_self_ty: Ty<'db>, + pick: &probe::Pick<'db>, + ) { + debug!( + "unify_receivers: self_ty={:?} method_self_ty={:?} pick={:?}", + self_ty, method_self_ty, pick + ); + let cause = ObligationCause::new(); + match self.ctx.table.at(&cause).sup(method_self_ty, self_ty) { + Ok(infer_ok) => { + self.ctx.table.register_infer_ok(infer_ok); + } + Err(_) => { + if self.ctx.unstable_features.arbitrary_self_types { + self.ctx.result.type_mismatches.insert( + self.expr.into(), + TypeMismatch { expected: method_self_ty, actual: self_ty }, + ); + } + } + } + } + + // NOTE: this returns the *unnormalized* predicates and method sig. Because of + // inference guessing, the predicates and method signature can't be normalized + // until we unify the `Self` type. + fn instantiate_method_sig<'c>( + &mut self, + pick: &probe::Pick<'db>, + all_args: &'c [GenericArg<'db>], + ) -> (FnSig<'db>, impl Iterator<Item = PredicateObligation<'db>> + use<'c, 'db>) { + debug!("instantiate_method_sig(pick={:?}, all_args={:?})", pick, all_args); + + // Instantiate the bounds on the method with the + // type/early-bound-regions instantiations performed. There can + // be no late-bound regions appearing here. + let def_id = self.candidate; + let method_predicates = clauses_as_obligations( + GenericPredicates::query_all(self.db(), def_id.into()) + .iter_instantiated_copied(self.interner(), all_args), + ObligationCause::new(), + self.ctx.table.trait_env.env, + ); + + let sig = + self.db().callable_item_signature(def_id.into()).instantiate(self.interner(), all_args); + debug!("type scheme instantiated, sig={:?}", sig); + + let sig = self.instantiate_binder_with_fresh_vars(sig); + debug!("late-bound lifetimes from method instantiated, sig={:?}", sig); + + (sig, method_predicates) + } + + fn add_obligations( + &mut self, + sig: FnSig<'db>, + all_args: GenericArgs<'db>, + method_predicates: impl Iterator<Item = PredicateObligation<'db>>, + ) { + debug!("add_obligations: sig={:?} all_args={:?}", sig, all_args); + + self.ctx.table.register_predicates(method_predicates); + + // this is a projection from a trait reference, so we have to + // make sure that the trait reference inputs are well-formed. + self.ctx.table.add_wf_bounds(all_args); + + // the function type must also be well-formed (this is not + // implied by the args being well-formed because of inherent + // impls and late-bound regions - see issue #28609). + for ty in sig.inputs_and_output { + self.ctx.table.register_wf_obligation(ty.into(), ObligationCause::new()); + } + } + + /////////////////////////////////////////////////////////////////////////// + // MISCELLANY + + fn predicates_require_illegal_sized_bound( + &self, + predicates: impl Iterator<Item = Clause<'db>>, + ) -> bool { + let Some(sized_def_id) = + LangItem::Sized.resolve_trait(self.db(), self.ctx.resolver.krate()) + else { + return false; + }; + + elaborate(self.interner(), predicates) + // We don't care about regions here. + .filter_map(|pred| match pred.kind().skip_binder() { + ClauseKind::Trait(trait_pred) if trait_pred.def_id().0 == sized_def_id => { + Some(trait_pred) + } + _ => None, + }) + .any(|trait_pred| matches!(trait_pred.self_ty().kind(), TyKind::Dynamic(..))) + } + + fn check_for_illegal_method_calls(&self) { + // Disallow calls to the method `drop` defined in the `Drop` trait. + if let ItemContainerId::TraitId(trait_def_id) = self.candidate.loc(self.db()).container + && LangItem::Drop + .resolve_trait(self.db(), self.ctx.resolver.krate()) + .is_some_and(|drop_trait| drop_trait == trait_def_id) + { + // FIXME: Report an error. + } + } + + #[expect(clippy::needless_return)] + fn lint_shadowed_supertrait_items(&self, pick: &probe::Pick<'_>) { + if pick.shadowed_candidates.is_empty() { + return; + } + + // FIXME: Emit the lint. + } + + fn upcast( + &self, + source_trait_ref: PolyTraitRef<'db>, + target_trait_def_id: TraitId, + ) -> PolyTraitRef<'db> { + let upcast_trait_refs = + upcast_choices(self.interner(), source_trait_ref, target_trait_def_id); + + // must be exactly one trait ref or we'd get an ambig error etc + if let &[upcast_trait_ref] = upcast_trait_refs.as_slice() { + upcast_trait_ref + } else { + Binder::dummy(TraitRef::new_from_args( + self.interner(), + target_trait_def_id.into(), + GenericArgs::error_for_item(self.interner(), target_trait_def_id.into()), + )) + } + } + + fn instantiate_binder_with_fresh_vars<T>(&self, value: Binder<'db, T>) -> T + where + T: TypeFoldable<DbInterner<'db>> + Copy, + { + self.infcx().instantiate_binder_with_fresh_vars(BoundRegionConversionTime::FnCall, value) + } +} |