Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4681,6 +4681,7 @@ dependencies = [
name = "rustc_type_ir"
version = "0.0.0"
dependencies = [
"arrayvec",
"bitflags",
"derive-where",
"ena",
Expand Down
12 changes: 4 additions & 8 deletions compiler/rustc_hir_analysis/src/autoderef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,10 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> {
Some((normalized_ty, ocx.into_pending_obligations()))
}

/// Returns the final type we ended up with, which may be an inference
/// variable (we will resolve it first, if we want).
pub fn final_ty(&self, resolve: bool) -> Ty<'tcx> {
if resolve {
self.infcx.resolve_vars_if_possible(self.state.cur_ty)
} else {
self.state.cur_ty
}
/// Returns the final type we ended up with, which may be an unresolved
/// inference variable.
pub fn final_ty(&self) -> Ty<'tcx> {
self.state.cur_ty
}

pub fn step_count(&self) -> usize {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/autoderef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

let mut obligations = PredicateObligations::new();
let targets =
steps.iter().skip(1).map(|&(ty, _)| ty).chain(iter::once(autoderef.final_ty(false)));
steps.iter().skip(1).map(|&(ty, _)| ty).chain(iter::once(autoderef.final_ty()));
let steps: Vec<_> = steps
.iter()
.map(|&(source, kind)| {
Expand Down
122 changes: 118 additions & 4 deletions compiler/rustc_hir_typeck/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rustc_middle::ty::adjustment::{
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::{bug, span_bug};
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_span::{Span, Symbol, sym};
use rustc_target::spec::{AbiMap, AbiMapping};
use rustc_trait_selection::error_reporting::traits::DefIdOrName;
use rustc_trait_selection::infer::InferCtxtExt as _;
Expand Down Expand Up @@ -78,15 +78,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => self.check_expr(callee_expr),
};

let expr_ty = self.structurally_resolve_type(call_expr.span, original_callee_ty);
let expr_ty = self.try_structurally_resolve_type(call_expr.span, original_callee_ty);

let mut autoderef = self.autoderef(callee_expr.span, expr_ty);
let mut result = None;
while result.is_none() && autoderef.next().is_some() {
result = self.try_overloaded_call_step(call_expr, callee_expr, arg_exprs, &autoderef);
}

match autoderef.final_ty(false).kind() {
match autoderef.final_ty().kind() {
ty::FnDef(def_id, _) => {
let abi = self.tcx.fn_sig(def_id).skip_binder().skip_binder().abi;
self.check_call_abi(abi, call_expr.span);
Expand Down Expand Up @@ -201,7 +201,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
autoderef: &Autoderef<'a, 'tcx>,
) -> Option<CallStep<'tcx>> {
let adjusted_ty =
self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false));
self.try_structurally_resolve_type(autoderef.span(), autoderef.final_ty());

// If the callee is a function pointer or a closure, then we're all set.
match *adjusted_ty.kind() {
Expand Down Expand Up @@ -298,6 +298,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return None;
}

ty::Infer(ty::TyVar(vid)) => {
// If we end up with an inference variable which is not the hidden type of
// an opaque, emit an error.
if let Some(alias_ty) = self.find_opaque_type_related_to_vid(vid) {
return self
.try_overloaded_call_traits_for_alias(call_expr, alias_ty, arg_exprs)
.map(|(autoref, method)| {
let mut adjustments = self.adjust_steps(autoderef);
adjustments.extend(autoref);
self.apply_adjustments(callee_expr, adjustments);
CallStep::Overloaded(method)
});
} else {
self.type_must_be_known_at_this_point(autoderef.span(), adjusted_ty);
return None;
}
}

ty::Error(_) => {
return None;
}
Expand Down Expand Up @@ -402,6 +420,102 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None
}

fn try_overloaded_call_trait(
&self,
call_expr: &hir::Expr<'_>,
call_ty: Ty<'tcx>,
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
opt_trait_def_id: Option<DefId>,
method_name: Symbol,
borrow: bool,
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
let Some(trait_def_id) = opt_trait_def_id else {
return None;
};

let opt_input_type = opt_arg_exprs.map(|arg_exprs| {
Ty::new_tup_from_iter(self.tcx, arg_exprs.iter().map(|e| self.next_ty_var(e.span)))
});

let Some(ok) = self.lookup_method_for_operator(
self.misc(call_expr.span),
method_name,
trait_def_id,
call_ty,
opt_input_type,
) else {
return None;
};
let method = self.register_infer_ok_obligations(ok);
let mut autoref = None;
if borrow {
// Check for &self vs &mut self in the method signature. Since this is either
// the Fn or FnMut trait, it should be one of those.
let ty::Ref(_, _, mutbl) = *method.sig.inputs()[0].kind() else {
bug!("Expected `FnMut`/`Fn` to take receiver by-ref/by-mut")
};

// For initial two-phase borrow
// deployment, conservatively omit
// overloaded function call ops.
let mutbl = AutoBorrowMutability::new(mutbl, AllowTwoPhase::No);

autoref = Some(Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
target: method.sig.inputs()[0],
});
}

Some((autoref, method))
}

fn try_overloaded_call_traits_for_alias(
&self,
call_expr: &'tcx hir::Expr<'tcx>,
alias_ty: ty::AliasTy<'tcx>,
arg_exprs: &'tcx [rustc_hir::Expr<'tcx>],
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
let call_ty = alias_ty.to_ty(self.tcx);

let call_traits = [
(self.tcx.lang_items().fn_trait(), sym::call, true),
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
];
// We only want to try a call trait if it shows up in the bounds
// of the opaque. We confirm the first one that shows up in the
// bounds list, which can lead to inference weirdness but doesn't
// matter today.
for clause in
self.tcx.item_self_bounds(alias_ty.def_id).iter_instantiated(self.tcx, alias_ty.args)
{
Comment on lines +490 to +494
Copy link
Contributor Author

@lcnr lcnr Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The desired behavior is quite 🤷 here...

if we've got impl FnMut(u32) + FnMut(u32) we should prefer the Fn bound. We should do for call_trait { for item bound { check }}.

If we've got impl Fn(&'a str) + Fn(&'b str) or sth that should be handled fine, even for non-defining uses. Needs tests

Even if we've got impl Fn(T) as the opaque type, rn we don't use this at all, but instead lookup_method_for_operator just emits all the bounds necessary to use that method, so we then rely on incomplete item bounds jank to actually constrain the args of the call.

This is necessary to support stuff like

fn recur() -> impl Fn(fn(&str) -> usize) {
   if false {
       let x = recur();
       x(|s| s.len());
   }

   |_func| ()
}

the other issue is handling

fn recur() -> impl Sized {
   if false {
       let x = recur();
       x();
   }

   || ()
}

this should result in a "type must be known at this point error, not a "type doesn't implement fn"

I guess I am gonna block this PR on getting item bounds working as otherwise we're missing some important tests here

let Some(poly_trait_ref) = clause.as_trait_clause() else {
continue;
};

if let Some(&(opt_trait_def_id, method_name, borrow)) =
call_traits.iter().find(|(trait_def_id, _, _)| {
trait_def_id.is_some_and(|trait_def_id| trait_def_id == poly_trait_ref.def_id())
})
&& let Some(confirmed) = self.try_overloaded_call_trait(
call_expr,
call_ty,
Some(arg_exprs),
opt_trait_def_id,
method_name,
borrow,
)
{
return Some(confirmed);
}
}

None
}

/// Give appropriate suggestion when encountering `||{/* not callable */}()`, where the
/// likely intention is to call the closure, suggest `(||{})()`. (#55851)
fn identify_bad_closure_def_and_call(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2918,7 +2918,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Emits an error if we deref an infer variable, like calling `.field` on a base type
// of `&_`. We can also use this to suppress unnecessary "missing field" errors that
// will follow ambiguity errors.
let final_ty = self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false));
let final_ty = self.structurally_resolve_type(autoderef.span(), autoderef.final_ty());
if let ty::Error(_) = final_ty.kind() {
return final_ty;
}
Expand Down
37 changes: 19 additions & 18 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,24 +1469,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(crate) fn structurally_resolve_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
let ty = self.try_structurally_resolve_type(sp, ty);

if !ty.is_ty_var() {
ty
} else {
let e = self.tainted_by_errors().unwrap_or_else(|| {
self.err_ctxt()
.emit_inference_failure_err(
self.body_id,
sp,
ty.into(),
TypeAnnotationNeeded::E0282,
true,
)
.emit()
});
let err = Ty::new_error(self.tcx, e);
self.demand_suptype(sp, err, ty);
err
}
if !ty.is_ty_var() { ty } else { self.type_must_be_known_at_this_point(sp, ty) }
}

#[cold]
pub(crate) fn type_must_be_known_at_this_point(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
let guar = self.tainted_by_errors().unwrap_or_else(|| {
self.err_ctxt()
.emit_inference_failure_err(
self.body_id,
sp,
ty.into(),
TypeAnnotationNeeded::E0282,
true,
)
.emit()
});
let err = Ty::new_error(self.tcx, guar);
self.demand_suptype(sp, err, ty);
err
}

pub(crate) fn structurally_resolve_const(
Expand Down
7 changes: 0 additions & 7 deletions compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use rustc_middle::ty::{self, Const, Ty, TyCtxt, TypeVisitableExt};
use rustc_session::Session;
use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span, sym};
use rustc_trait_selection::error_reporting::TypeErrCtxt;
use rustc_trait_selection::error_reporting::infer::sub_relations::SubRelations;
use rustc_trait_selection::traits::{
self, FulfillmentError, ObligationCause, ObligationCauseCode, ObligationCtxt,
};
Expand Down Expand Up @@ -188,14 +187,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
///
/// [`InferCtxtErrorExt::err_ctxt`]: rustc_trait_selection::error_reporting::InferCtxtErrorExt::err_ctxt
pub(crate) fn err_ctxt(&'a self) -> TypeErrCtxt<'a, 'tcx> {
let mut sub_relations = SubRelations::default();
sub_relations.add_constraints(
self,
self.fulfillment_cx.borrow_mut().pending_obligations().iter().map(|o| o.predicate),
);
TypeErrCtxt {
infcx: &self.infcx,
sub_relations: RefCell::new(sub_relations),
typeck_results: Some(self.typeck_results.borrow()),
fallback_has_occurred: self.fallback_has_occurred.get(),
normalize_fn_sig: Box::new(|fn_sig| {
Expand Down
10 changes: 4 additions & 6 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// special handling for this "trivial case" is a good idea.

let infcx = &self.infcx;
let (ParamEnvAnd { param_env: _, value: self_ty }, canonical_inference_vars) =
let (ParamEnvAnd { param_env: _, value: self_ty }, var_values) =
infcx.instantiate_canonical(span, &query_input.canonical);
debug!(?self_ty, ?query_input, "probe_op: Mode::Path");
MethodAutoderefStepsResult {
steps: infcx.tcx.arena.alloc_from_iter([CandidateStep {
self_ty: self.make_query_response_ignoring_pending_obligations(
canonical_inference_vars,
self_ty,
),
self_ty: self
.make_query_response_ignoring_pending_obligations(var_values, self_ty),
autoderefs: 0,
from_unsafe_deref: false,
unsize: false,
Expand Down Expand Up @@ -629,7 +627,7 @@ pub(crate) fn method_autoderef_steps<'tcx>(
.collect();
(steps, autoderef_via_deref.reached_recursion_limit())
};
let final_ty = autoderef_via_deref.final_ty(true);
let final_ty = autoderef_via_deref.final_ty();
let opt_bad_ty = match final_ty.kind() {
ty::Infer(ty::TyVar(_)) | ty::Error(_) => Some(MethodAutoderefBadTy {
reached_raw_pointer,
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_hir_typeck/src/place_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
index_ty: Ty<'tcx>,
index_expr: &hir::Expr<'_>,
) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> {
let adjusted_ty =
self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false));
let adjusted_ty = self.structurally_resolve_type(autoderef.span(), autoderef.final_ty());
debug!(
"try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \
index_ty={:?})",
Expand Down
Loading
Loading